Опубликован: 11.01.2013 | Доступ: свободный | Студентов: 623 / 124 | Длительность: 12:06:00
Лекция 3:

Лабораторный практикум 2

< Лекция 2 || Лекция 3: 12345 || Лекция 4 >

Лабораторная работа №9. Игра "Загони шар в лузу"

Задание

Создать игру "Загони шар в лузу" для Windows Phone 7 с использованием акселерометра (шар меняет направление и скорость при наклоне телефона) и возможностью сохранения наилучшего времени в изолированном хранилище.

Освоение

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

Описание

Создадим новый проект Silverlight for Windows PhoneWindows Phone Application.

Откроем файл разметки главной страницы MainPage.xaml. Разместим элемент Canvas, в котором будут находиться два эллипса (элемент управления Ellipse). В зависимости от положения телефона будем менять свойства Canvas.Left и Canvas.Top. Добавим также текстовый блок для вывода времени игры и 3 кнопки: новая игра, просмотр рекорда и сброс рекорда.

<!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel>
                <Canvas Name="cnvMain" Height="700" Width="450" Background="White">
                    <Ellipse Name="ellBall" Height="30" Width="30" Canvas.Left="50" Canvas.Top="200" Canvas.ZIndex="2" 
                    Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" Fill="Red" />
                    <Ellipse Name="ellHole" Height="50" Width="50" Canvas.Left="200" Canvas.Top="50" Canvas.ZIndex="1"
                     Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" Fill="Black" />
                </Canvas>

                <StackPanel Orientation="Horizontal">
                    <TextBlock Name="lblTime" Text="00:00" FontSize="26" VerticalAlignment="Center" />
                    <Button Name="btnNewGame" Content="Новая" Click="btnNewGame_Click" />
                    <Button Name="btnRecord" Content="Рекорд" Click="btnRecord_Click" />
                    <Button Name="btnResetRecord" Content="Сброс" Click="btnResetRecord_Click" />
                </StackPanel>
            </StackPanel>
        </Grid>

Добавим константы для определения радиусов шарика и лузы:

        private const double BALL_RAD = 15d;
        private const double HOLE_RAD = 20d;

В конструкторе класса проинициализируем размеры элепсов:

       ellBall.Width = BALL_RAD * 2d;
            ellBall.Height = BALL_RAD * 2d;
            ellHole.Width = HOLE_RAD * 2d;
            ellHole.Height = HOLE_RAD * 2d;

Для работы с акселерометром добавим в код директиву:

  using Microsoft.Devices.Sensors;     

Создадим глобальную переменную акселерометра:

    private Accelerometer myAccel;   

В конструкторе класса страницы проинициализируем акселерометр и подпишемся на событие изменения положения телефона:

   myAccel = new Accelerometer();
        myAccel.ReadingChanged += new EventHandler<AccelerometerReadingEventArgs>(myAccel_ReadingChanged);

Для изменения скорости шарика определим глобальную константу и переменные в классе:

  private const double KOEFF_ACCEL = 20d;
        private double nSpeedUpZ, nSpeedUpX;
        private bool bGameStopped;

При изменении положения телефона будем менять координаты шарика. Поскольку из отдельного потока нельзя напрямую работать с элементами управления, будем пользоваться диспетчерами. После изменения координат будем проверять игру на победу (попадание центра шарика в лузу).

 void myAccel_ReadingChanged(object sender, AccelerometerReadingEventArgs e)
        {
            if (myAccel.IsDataValid && !bGameStopped)
            {
                nSpeedUpZ = e.Z * KOEFF_ACCEL;
                nSpeedUpX = e.X * KOEFF_ACCEL;

                if (e.Z < 0)
                {
                    Dispatcher.BeginInvoke(() => HandleUp());
                }
                else
                {
                    Dispatcher.BeginInvoke(() => HandleDown());
                }

                if (e.X < 0)
                {
                    Dispatcher.BeginInvoke(() => HandleLeft());
                }
                else
                {
                    Dispatcher.BeginInvoke(() => HandleRight());
                }

                Dispatcher.BeginInvoke(() => WinChecking());
            }
        }

        private void HandleUp()
        {
            if ((double)ellBall.GetValue(Canvas.TopProperty) > 0d)
            {
                ellBall.SetValue(Canvas.TopProperty, (double)ellBall.GetValue(Canvas.TopProperty) + nSpeedUpZ);
            }
        }

        private void HandleDown()
        {
            if ((double)ellBall.GetValue(Canvas.TopProperty) < (cnvMain.Height - ellBall.Height))
            {
                ellBall.SetValue(Canvas.TopProperty, (double)ellBall.GetValue(Canvas.TopProperty) + nSpeedUpZ);
            }
        }

        private void HandleLeft()
        {
            if ((double)ellBall.GetValue(Canvas.LeftProperty) > 0d)
            {
                ellBall.SetValue(Canvas.LeftProperty, (double)ellBall.GetValue(Canvas.LeftProperty) + nSpeedUpX);
            }
        }

        private void HandleRight()
        {
            if ((double)ellBall.GetValue(Canvas.LeftProperty) < (cnvMain.Width - ellBall.Width))
            {

                ellBall.SetValue(Canvas.LeftProperty, (double)ellBall.GetValue(Canvas.LeftProperty) + nSpeedUpX);
            }
        }

        private void WinChecking()
        {
            if (IsWin())
            {
                bGameStopped = true;
                dTimer.Stop();
                myAccel.Stop();

                if (IsRecord())
                {
                    nTimeRecord = nTime;
                    MessageBox.Show("Победа!" + Environment.NewLine + "Новый рекорд: " + FormatTime(nTimeRecord) + "!");

                    SaveToIsolatedStorage(nTimeRecord.ToString());
                }
                else
                {
                    MessageBox.Show("Победа!");
                }
            }
        }

        private bool IsWin()
        {
            bool bRes = false;

            if ((((double)ellBall.GetValue(Canvas.TopProperty) + BALL_RAD) > 
            ((double)ellHole.GetValue(Canvas.TopProperty))) &&
                (((double)ellBall.GetValue(Canvas.TopProperty) + BALL_RAD) < 
                ((double)ellHole.GetValue(Canvas.TopProperty) + HOLE_RAD + HOLE_RAD)))
            {
                if ((((double)ellBall.GetValue(Canvas.LeftProperty) + BALL_RAD) > 
                ((double)ellHole.GetValue(Canvas.LeftProperty))) &&
                (((double)ellBall.GetValue(Canvas.LeftProperty) + BALL_RAD) < 
                ((double)ellHole.GetValue(Canvas.LeftProperty) + HOLE_RAD + HOLE_RAD)))
                {
                    bRes = true;
                }
            }

            return bRes;
        }

Введем в игру счетчик времени. Добавим константу и переменные:

 private const int MAX_TIME = 3599; //59:59
        private int nTimeRecord;
        private int nTime;
        System.Windows.Threading.DispatcherTimer dTimer;

В конструкторе класса объявим новый поток и подпишемся на событие срабатывания таймера (каждую секунду):

             dTimer = new System.Windows.Threading.DispatcherTimer();
            dTimer.Interval = new TimeSpan(0, 0, 0, 1, 0); //1 сек
            dTimer.Tick += new EventHandler(Timer_Tick);

По событию будем увеличивать таймер и с помощью диспетчера менять время в текстовом блоке на странице. А в случае, если максимальное время достигнуто, останавливаем игру.

    private void Timer_Tick(object sender, EventArgs e)
        {
            nTime++;
            Dispatcher.BeginInvoke(() => ChangeTimerLabel());

            if (IsLose())
            {
                Dispatcher.BeginInvoke(() => Lose());
            }
        }

        private void ChangeTimerLabel()
        {
            lblTime.Text = FormatTime(nTime);
        }

        private string FormatTime(int time)
        {
            return String.Format("{0:00}", (int)(time / 60)) + ":" + String.Format("{0:00}", (time % 60));
        }

        private bool IsLose()
        {
            bool bRes = false;

            if (nTime >= MAX_TIME)
            {
                bRes = true;
            }

            return bRes;
        }

        private void Lose()
        {
            bGameStopped = true;
            dTimer.Stop();
            myAccel.Stop();

            MessageBox.Show("Скучно...");
        }
 

В случае успешного попадания шарика в лузу, если достигнут рекорд по времени, сохраняем его в файле в изолированном хранилище:

  private const string strIStorageName = "Wp7IUSLab12.txt";

При этому в конструкторе класса, в случае если файл с рекордом существует, будем загружать рекорд:

if (IsIsolatedStorageExist())
   {
       nTimeRecord = int.Parse(LoadFromIsolatedStorage());
   }
   else
   {
       nTimeRecord = MAX_TIME;
   }

При нажатии на кнопку "Сброс" будем очищать хранилище:

Для работы с хранилищем определим директивы:

        using System.IO.IsolatedStorage;
        using System.IO;

Создадим функции сохранения рекорда в хранилище, загрузки, удаления и проверки файла на существование:

    private void SaveToIsolatedStorage(string histText)
        {
            IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
            IsolatedStorageFileStream fileStream = fileStorage.CreateFile(strIStorageName);

            StreamWriter sw = new StreamWriter(fileStream);
            sw.Write(histText);
            sw.Close();

            fileStream.Close();
        }

        private string LoadFromIsolatedStorage()
        {
            IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
            IsolatedStorageFileStream fileStream = fileStorage.OpenFile(strIStorageName, System.IO.FileMode.Open);

            StreamReader sr = new StreamReader(fileStream);
            string strRes = sr.ReadToEnd();
            sr.Close();
            fileStream.Close();

            return strRes;
        }

        private bool IsIsolatedStorageExist()
        {
            IsolatedStorageFile sileStorage = IsolatedStorageFile.GetUserStoreForApplication();
            return sileStorage.FileExists(strIStorageName);
        }

        private void RemoveIsolatedStorage()
        {
            if (IsIsolatedStorageExist())
            {
                IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
                fileStorage.DeleteFile(strIStorageName);
            }
        }

Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.

< Лекция 2 || Лекция 3: 12345 || Лекция 4 >