Игра Мозаика похожа на пятнашки, только вместо цифр картинка. Загружается желаемое изображение. Изображение делится на сегменты, сегменты перемешиваются, один сегмент исчезает. Необходимо передвигая сегменты на пустое место собрать картинку полностью.
Исходный код игры Мозаика состоит из двух логических блоков: управление игрой и методы непосредственно с логикой игры. Управление игрой построено на событиях, которые вызывают соответствующие логические методы.
Загрузка изображения осуществляется событием нажатия кнопки в панели инструментов. При этом загружаемая картинка разделяется на установленное количество сегментов, которые первично расположены по порядку.
/// <summary>
/// Загрузка картинки.
/// </summary>
private void ToolStripButtonLoadPicture_Click(object sender, EventArgs e)
{
LoadPicture();
}
/// <summary>
/// Загрузка картинки и создание сегментов.
/// </summary>
private void LoadPicture()
{
var ofDlg = new OpenFileDialog();
// Фильтр показа изображений с определенным расширением.
ofDlg.Filter = "файлы картинок (*.bmp;*.jpg;*.jpeg;*.gif;)|";
ofDlg.Filter += "*.bmp;*.jpg;*.jpeg;*.gif|All files (*.*)|*.*";
ofDlg.FilterIndex = 1;
ofDlg.RestoreDirectory = true;
if (ofDlg.ShowDialog() == DialogResult.OK)
{
// Загружаем выбранную картинку.
Picture = new Bitmap(ofDlg.FileName);
// Создание сегментов
CreatePictureSegments();
}
}
Сегменты для хранения частей разделенной картинки представляют собой массив элементов класса class PictureBox. В каждом сегменте хранится своя порция изображения. Когда сегменты расположены по порядку изображение визуально выглядит как единое целое. Размер каждого сегмента вычисляется исходя из размеров клиентской части окна и количества сегментов в одном ряду. Каждый сегмент хранит информацию о начальной позиции, чтобы можно было восстановить картинку. Начальная позиция используется также для определения успешной сборки изображения.
/// <summary>
/// Создание сегментов картинки
/// </summary>
private void CreatePictureSegments()
{
// Удалим предыдущий массив, чтобы создать новый.
if (pbSegments != null)
{
for (int i = 0; i < pbSegments.Length; i++)
{
pbSegments[i].Dispose();
}
pbSegments = null;
}
// Создаем массив прямоугольников установленного размера.
pbSegments = new PictureBox[numRect * numRect];
// Вычислим габаритные размеры прямоугольников.
int w = ClientSize.Width / numRect;
int h = ClientSize.Height / numRect;
// Счетчики порядкового номера по координатам Х и Y.
int countX = 0;
int countY = 0;
for (int i = 0; i < pbSegments.Length; i++)
{
// Размеры и координаты размещения созданного прямоугольника.
pbSegments[i] = new PictureBox
{
Width = w,
Height = h,
Left = countX * w,
Top = countY * h
};
// Запомним начальные координаты прямоугольника для
// восстановления перемешанной картинки,
// и определения полной сборки картинки.
Point pt = new Point();
pt.X = pbSegments[i].Left;
pt.Y = pbSegments[i].Top;
// сохраним координаты в свойстве Tag каждого прямоугольника
pbSegments[i].Tag = pt;
// Считаем прямоугольники по рядам и столбцам.
countX++;
if (countX == numRect)
{
countX = 0;
countY++;
}
pbSegments[i].Parent = this;
pbSegments[i].BorderStyle = BorderStyle.None;
pbSegments[i].SizeMode = PictureBoxSizeMode.StretchImage;
// Новые сегменты должны быть все видимы.
pbSegments[i].Show();
// Для всех прямоугольников массива событие клика мыши
// будет обрабатываться в одной и том же методе.
pbSegments[i].Click += new EventHandler(PB_Click);
}// for (int i = 0; i < pbSegments.Length; i++)
DrawPicture();
}
В сформированные сегменты копируется изображение. В соответствии со своей позицией каждый сегмент получает свою долю картинки. Размер части картинки, также как и сегмента, автоматически высчитывается получая данные о количестве сегментов и размере клиентской части окна.
/// <summary>
/// Копируем части картинки в соответствующие боксы и
/// боксы рисуют свою порцию изображения.
/// </summary>
private void DrawPicture()
{
if (Picture == null) return;
int countX = 0;
int countY = 0;
for (int i = 0; i < pbSegments.Length; i++)
{
int w = Picture.Width / numRect;
int h = Picture.Height / numRect;
pbSegments[i].Image =
Picture.Clone(new RectangleF(countX * w, countY * h, w, h),
Picture.PixelFormat);
countX++;
if (countX == numRect)
{
countX = 0;
countY++;
}
}
}
При изменениях размеров окна изменяется и размер клиентской части, в которую вписана картинка. Чтобы изображение корректно отображалось при любом размере окна, габариты сегментов пересчитываются при генерировании события формы FormMain.SizeChanged. Благодаря установленному свойству сегментов SizeMode = StretchImage изображение сохраняет целостный вид при любых изменениях размеров окна приложения.
/// <summary>
/// Корректировка размеров сегментов при изменении размеров окна.
/// </summary>
private void CorrectSizeSegments()
{
if (pbSegments == null) return;
// Предыдущие размеры сегментов
int oldwidth = pbSegments[0].Width;
int oldheight = pbSegments[0].Height;
// Новые размеры прямоугольников.
int w = ClientSize.Width / numRect;
int h = ClientSize.Height / numRect;
//int countX = 0; // счетчик прямоугольников по координате X в одном ряду
//int countY = 0; // счетчик прямоугольников по координате Y в одном столбце
for (int i = 0; i < pbSegments.Length; i++)
{
pbSegments[i].Width = w;
pbSegments[i].Height = h;
// Получим порядковый номер сегмента по координате Х
int countX = pbSegments[i].Left /= oldwidth;
// Получим порядковый номер сегмента по координате Y
int countY = pbSegments[i].Top /= oldheight;
pbSegments[i].Left = countX * w;
pbSegments[i].Top = countY * h;
}
}
Чтобы игра Мозаика началась необходимо перемешать сегменты картинки. Для данного типа игры достаточно использовать генератор псевдослучайных чисел class Random . Перемешивание заключается в рандомном обмене позиций расположения сегментов. Но благодаря тому, что в свойстве Tag сегмента хранится начальная позиция прямоугольника очень легко восстановить изображение вновь.
/// <summary>
/// Перемешивание сегментов
/// </summary>
private void MixedSegments()
{
if (Picture == null) return;
// Создаем объект генерирования псевдослучайных чисел,
// для различного набора случайных чисел инициализацию
// объекта Random производим от счетчика количества
// миллисекунд прошедших со времени запуска операционной системы.
Random rand = new Random(Environment.TickCount);
for (int i = 0; i < pbSegments.Length; i++)
{
pbSegments[i].Visible = true;
int temp = rand.Next(0, pbSegments.Length);
Point ptR = pbSegments[temp].Location;
Point ptI = pbSegments[i].Location;
pbSegments[i].Location = ptR;
pbSegments[temp].Location = ptI;
// Бордюр чтобы видно было прямоугольники
pbSegments[i].BorderStyle = BorderStyle.Fixed3D;
}
// Случайным образом выбираем пустой прямоугольник,
// делаем его невидимым.
int r = rand.Next(0, pbSegments.Length);
pbSegments[r].Visible = false;
}
После каждого хода происходит проверка на полную сборку изображения. Как уже было описано выше, каждый сегмент хранит информацию о своей начальной позиции. При перемещении прямоугольника на новую позицию сравниваются текущая позиция и начальная. Если эти позиции совпадают значит картинка полностью собрана. В этом случае на сегментах исчезает бордюр, пустое место для перемещения заполняется недостающей частью изображения, игра заканчивается.
// После каждого хода проверка на полную сборку картинки.
//*************** блок проверки ***********************
// Если хоть у одного прямоугольника не совпадают
// реальные координаты и первичные заканчиваем
// проверку и выходим из метода.
for (int j = 0; j < pbSegments.Length; j++)
{
Point point = (Point)pbSegments[j].Tag;
if (pbSegments[j].Location != point)
{
return;
}
}
// Если у всех прямоугольников совпали реальные и первичные
// координаты - картинка собрана!
for (int m = 0; m < pbSegments.Length; m++)
{
// Делаем видимыми все сегменты картинки.
pbSegments[m].Visible = true;
// Убираем обрамление прямоугольников.
pbSegments[m].BorderStyle = BorderStyle.None;
}
//************** окончание блока проверки *************