Розробка програми для розгадування філіппінських кросвордів на C++ в середовищі Marmalade SDK

Пара слів про те, що ж таке філіппінський кросворд
Філіппінський кросворд – це такий вид графічних головоломок, в яких з допомогою безлічі пар чисел зашифрована картинка. Всі кросворди повинні мати єдине рішення. Необхідно підібрати і з'єднати пари однакових чисел лініями так, результат задовольняв наступним умовам (на які я буду неодноразово посилатися при подальшому викладі статті):

  • довжина кожної лінії повинна відповідати числам, розташованим на її кінцях;
  • лінії не повинні перетинатися один з одним і проходити через одні і ті ж клітини;
  • лінії можуть йти у вертикальному і горизонтальному напрямках, можуть переломлюватися, але не можуть проходити по діагоналі.
Так як одиниця не має пари, то вона зафарбований за замовчуванням. В результаті розв'язання кросворду, коли всі пари чисел (крім одиниць) з'єднані лініями, виходить певний малюнок. Ознайомлення з нижчеподаній gif-кою проллє світло на процес вирішення філіппінського кросворду:

image

Дана задача вирішувалася в середовищі Marmalade SDK з використанням мови програмування C++, в результаті чого були зібрані версії додатки під iOS і Android.

Подання стану кросворду в оперативній пам'яті
Сітка кросворду складається з комірок, кожна з яких може бути закрашена або не зафарбований (в різний момент часу), а так само мати або не мати в собі число (це властивість клітинки не змінюється).

Мінімальне число, використовуване в моїх кросвордах – 1, максимальна – 9. В залежності від того, яким чином лінія проходить через клітку, вона може бути зафарбований декількома способами (тобто мати кілька різних станів).

Перерахуємо їх:

  • 1 – клітина не зафарбований, тобто лінія через неї не проходить;
  • 2 – клітка закрашена, але лінія через неї також не проходить. В такому стані знаходяться всі клітини з числом 1.
  • 3 – лінія проходить через клітку вертикально;
  • 4 – лінія проходить через клітку горизонтально;
  • 5 – лінія проходить через верхню і ліву сторону квадрата клітини;
  • 6 – лінія проходить через нижню і ліву сторону квадрата клітини;
  • 7 – лінія проходить через верхню і праву сторону квадрата клітини;
  • 8 – лінія проходить через нижню і праву сторону квадрата клітини;
  • 9 – клітина є крайньою кліткою лінії і лінія проходить через ліву сторону квадрата клітини;
  • 10 – клітина є крайньою кліткою лінії і лінія проходить через праву сторону квадрата клітини;
  • 11 – клітина є крайньою кліткою лінії і лінія проходить через верхню сторону квадрата клітини;
  • 12 – клітина є крайньою кліткою лінії і лінія проходить через нижню сторону квадрата клітини.
Приклади таких станів наочно показані на малюнку нижче

В результаті розв'язання кросворду користувач отримує наступне зображення:



Перше, що приходить на розум як способу відображення стану кросворду — це двовимірний масив, тобто матриця char-елементів. Тип char має розмірність в 1 байт, тобто дозволяє зберігати одне з 2^8 = 256 станів.

Розіб'ємо 8 біт-байти на дві групи: молодші і старші біти. Отримуємо 4 біта в кожній групі, кожна група дає можливість зберігати 2^4 = 16 станів.

image

Таким чином, виділяємо 4 старших біта для зберігання інформації про спосіб зафарбовування осередку, а 4 молодших біта для зберігання інформації про кількість, зазначеному в комірці.

#define BYTE_NUMBER_PART 15 // в двійковій системі 00001111
#define BYTE_FLAG_PART 240 // в двійковій системі 11110000

Опис структури кросворду
struct JCStruct
{
bool Resolved; // ознака вирішення кросворду
char FileName[255]; // ім'я файлу завдання
char W; // ширина сітки
char H; // висота сітки

char M[MAX_PUZZLE_HW][MAX_PUZZLE_ HW]; // матриця стану осередків

char Vector[CHANGE_VECTOR_SIZE][3]; // вектор змін для можливості скасування дій [i, j, old_value]
int Vector_s; // vector start pointer (посилається на перший елемент черги)
int Vector_e; // vector end pointer (посилається на перший порожній елемент черги)

int DigitsCnt; // Кількість чисел у кросворді (потрібно для оптимізації алгоритму малювання чисел)
};



Оголошуємо функції, що дозволяють з допомогою двійкового множення отримати значення старшої (прапор зафарбування) та молодшої (число у клітинці) групи біт.

char GetNumberFromByte(char c) // функція повертає число, зазначене в комірці. Якщо числа немає, то функція поверне 0
{
return c & BYTE_NUMBER_PART;
}

char GetFlagFromByte (char c) // функція повертає прапор зафарбування осередку. Повертає 0, якщо комірка не зафарбований
{
return c & BYTE_FLAG_PART;
}

Кодуємо можливі прапори зафарбовування клітинок кросворду:

#define LN_ONE 16 // 00010000 клітка закрашена, але лінія ч/з неї не проходить. для клітин з числом 1
#define LN_VERTICAL 32 // 00100000 лінія проходить ч/з клітку вертикально;
#define LN_HORIZONTAL 48 // 00110000 лінія проходить ч/з клітку горизонтально;
#define LN_LEFT_TOP 64 // 01000000 лінія проходить ч/з нижню і праву сторону квадрата клітини;
#define LN_LEFT_BOTTOM 80 // 01010000 лінія проходить ч/з верхню і праву сторону квадрата клітини;
#define LN_RIGHT_TOP 96 // 01100000 лінія проходить ч/з нижню і ліву сторону квадрата клітини;
#define LN_RIGHT_BOTTOM 112 // 01110000 лінія проходить ч/з верхню і ліву сторону квадрата клітини;
#define LN_RIGHT 128 // 10000000 клітина є крайньою кліткою лінії і лінія проходить ч/з ліву сторону клітини;
#define LN_LEFT 144 // 10010000 клітина є крайньою кліткою лінії і лінія проходить ч/з праву сторону клітини;
#define LN_TOP 160 // 10100000 клітина є крайньою кліткою лінії і лінія проходить ч/з нижню сторону клітини;
#define LN_BOTTOM 176 // 10110000 клітина є крайньою кліткою лінії і лінія проходить ч/з верхню сторону клітини.

Приклад
JCStruct* jc;
...
if (GetFlagFromByte(jc->M[i][j]) == LN_HORIZONTAL) // якщо клітка закрашена і лінія проходить через неї по горизонталі 
{
}


Далі весь функціонал по візуалізації та зміни кросворду реалізуємо використовуючи вищенаведені константи та функції.

Сценарій з'єднання двох чисел лініями
Користувач тапає пальцем на якусь клітку з числом і веде від неї лінію до іншої клітки з таким же числом. При отжатии пальця, якщо лінія відповідає трьом правилам, описаним на початку статті, то лінія фіксується, результатом чого є фіксація стану клітин матриці, через які вона проходить.

Поточна лінія, яку користувач ведемо пальцем, відбивається в структурі CurrentLineStackStruct:

struct PointStruct
{
int x; // координата комірки сітки по горизонталі
int y; // координати комірки в сітці по вертикалі
};

struct CurrentLineStackStruct // стек, де буде зберігатися поточна лінія
{
char num; // число, з якого почали лінію
PointStruct stack[9]; // стек, в якому відображаються клітинки, через яку проходить поточна лінія
char len; // покажчик стека
};

CurrentLineStackStruct LineStack;

При ініціалізації, а також при кожному «отжатии» пальця від екрану цей стек ініціалізується в початковий стан викликом наступної функції:

void ClearCurrentStack()
{
LineStack.len = 0; // довжину стека обнуляем
LineStack.num = 0; // число, з якого стартує стек обнуляем
}

Обробка ведення пальця/стилуса по сітці кросворду здійснюється функцією Redraw, яка повертає true, якщо після обробки кросворд потрібно перемалювати, і false — в іншому випадку.

Redraw
/*
jc - покажчик на структуру JCStruct
DrawContext - структура-контекст, яка зберігає поточні геометричні параметри відтворення, зокрема:
jc_screen_x - координата x верхнього лівого кута сітки кросворду
jc_screen_y - координата y верхнього лівого кута сітки кросворду
cell_wh - висота і ширина клітинки кросворду
jc_screen_w - ширина всієї сітки кросворду
jc_screen_h - висота всієї сітки кросворду
*/

bool Redraw(int x, int y) // x,y -- координати пальця-стилуса
{
if (!jc->Resolved) // Вирішене кросворд можна міняти, можна тільки стерти всі
if ((DrawContext.jc_screen_x <= x) && (x < DrawContext.jc_screen_x + DrawContext.jc_screen_w))
if ((DrawContext.jc_screen_y <= y) && (y < DrawContext.jc_screen_y + DrawContext.jc_screen_h))
{
// отримуємо координати комірки
int i = (x - DrawContext.jc_screen_x) / DrawContext.cell_wh;
int j = (y - DrawContext.jc_screen_y) / DrawContext.cell_wh;

// Якщо стек ще не розпочато, а на комірці немає числа, то виходимо
char n = GetNumberFromByte(jc->M[i][j]);

if ((LineStack.len == 0) && (n == 0)) return false;

// Якщо комірка вже замальовано, то виходимо
if (GetFlagFromByte(jc->M[i][j]) > 0) return false;

// дивимося, може ми повернулися назад на одну з попередніх клітин стека
for (int s = 0; s < LineStack.len; s++)
if ((LineStack.stack[s].x == i) && (LineStack.stack[s].y == j))
{
// вкорочуємо стек
LineStack.len = s + 1;
return true;
}

// Якщо стек ще не розпочато, то починаємо його з того числа, на яке натиснули (те що це число випливає з умови вище)
if (LineStack.len == 0) 
{
// Стартуємо стек
LineStack.len++;
LineStack.num = n; // запам'ятовуємо число, з якого почали

// записуємо першу точку в стек
LineStack.stack[LineStack.len - 1].x = i;
LineStack.stack[LineStack.len - 1].y = j;

return true;
}
else // якщо стек вже розпочато, то значить це чергова осередок
{
// дивимося, щоб не було переповнення
// якщо є ще куди додавати клітинку (довжина лінії не може бути більше num)
if (LineStack.len < LineStack.num) 
{
// дивимося, щоб нова комірка була сусідній, по відношенню до останньої доданої
if ((abs(LineStack.stack[LineStack.len - 1].x - i) == 1 && LineStack.stack[LineStack.len - 1].y == j) 
||
(LineStack.stack[LineStack.len - 1].x == i && abs(LineStack.stack[LineStack.len - 1].y - j) == 1)
)
{
// сусідня комірка повинна бути або пустою, або мати таке число, як і те, 
// з якого почали, при цьому довжині лінії не повинно хвать саме цієї однієї клітини
if (n == 0 || LineStack.num == n && LineStack.len == n - 1)
{
LineStack.len++;
LineStack.stack[LineStack.len - 1].x = i;
LineStack.stack[LineStack.len - 1].y = j;
return true;
}

return false;
}

// Якщо нова комірка не сусідня, але лежить на одній горизонталі з останньою
if ((LineStack.stack[LineStack.len - 1].x != i) && (LineStack.stack[LineStack.len - 1].y == j))
{
int len = abs(i - LineStack.stack[LineStack.len - 1].x); // визначаємо довжину приросту
int d = (i - LineStack.stack[LineStack.len - 1].x) / len; // визначаємо напрямок (знак прирощення)

for (int s = 0; s < len; s++)
{
if (LineStack.len < LineStack.num)
{
// Якщо натрапили на зафарбовану клітинку
if (GetFlagFromByte(jc->M[LineStack.stack[LineStack.len - 1].x + d][j]) > 0) 
{
if (s > 0) return true; // Якщо перед цим вже щось додали, то робимо перемальовування
else return false; // Якщо нічого ще не додали, то оновлення не потрібна
}

n = GetNumberFromByte(jc->M[LineStack.stack[LineStack.len - 1].x + d][j]);

if (n > 0) // Якщо натрапили на числову клітинку
{
if (n != LineStack.num) // Якщо її значення не дорівнює числу, з якого почали
{
if (s > 0) return true; // Якщо перед цим вже щось додали, то робимо перемальовування
else return false; // Якщо нічого ще не додали, то оновлення не потрібна
}
else // Якщо число у клітинці дорівнює числу, з якого почали, тобто n == LineStack.num
{
if (LineStack.num != LineStack.len + 1) // Якщо не вистачає тільки однієї комірки
{
if (s > 0) return true; // Якщо перед цим вже щось додали, то робимо перемальовування
else return false; // Якщо нічого ще не додали, то оновлення не потрібна
}
}
}

LineStack.len++;
LineStack.stack[LineStack.len - 1].x = LineStack.stack[LineStack.len - 2].x + d;
LineStack.stack[LineStack.len - 1].y = j;
}
}

return true;
}

// Якщо нова комірка не сусідня, а лежить на одній вертикалі з останньою
if ((LineStack.stack[LineStack.len - 1].x == i) && (LineStack.stack[LineStack.len - 1].y != j))
{
int len = abs(j - LineStack.stack[LineStack.len - 1].y); // визначаємо довжину приросту
int d = (j - LineStack.stack[LineStack.len - 1].y) / len; // визначаємо напрямок (знак прирощення)

for (int s = 0; s < len; s++)
{
if (LineStack.len < LineStack.num)
{
// Якщо натрапили на зафарбовану клітинку
if (GetFlagFromByte(jc->M[i][LineStack.stack[LineStack.len - 1].y + d]) > 0) 
{
if (s > 0) return true; // Якщо перед цим вже щось додали, то робимо перемальовування
else return false; // Якщо нічого ще не додали, то оновлення не потрібна
}

n = GetNumberFromByte(jc->M[i][LineStack.stack[LineStack.len - 1].y + d]);

if (n > 0) // Якщо натрапили на числову клітинку
{
if (n != LineStack.num) // Якщо її значення не дорівнює числу, з якого почали
{
if (s > 0) return true; // Якщо перед цим вже щось додали, то робимо перемальовування
else return false; // Якщо нічого ще не додали, то оновлення не потрібна
}
else // Якщо число у клітинці дорівнює числу, з якого почали, тобто n == LineStack.num
{
if (LineStack.num != LineStack.len + 1) // Якщо не вистачає тільки однієї комірки
{
if (s > 0) return true; // Якщо перед цим вже щось додали, то робимо перемальовування
else return false; // Якщо нічого ще не додали, то оновлення не потрібна
}
}
}

LineStack.len++;
LineStack.stack[LineStack.len - 1].x = i;
LineStack.stack[LineStack.len - 1].y = LineStack.stack[LineStack.len - 2].y + d;
}
}

return true;
}

return false;
}
else // якщо переповнення, то виходимо
{
return false;
}
}
}

return false;
}


При «отжатии» пальця/стилуса справах обробку стека поточної лінії. Необхідно зрозуміти, чи правильно намальована лінія, тобто перевірити її на коректність, і у випадку коректності оновити відповідні клітинки матриці.

CheckCurrentLineStack
/* тут ми повинні прийняти рішення про те, 
що робити з поточним стеком лінії - заносити її в основну матрицю перед тим, як стерти
*/
void CheckCurrentLineStack() 
{
// Якщо стек не порожній, якщо довжина стека відповідає першому числу, 
// якщо число, з якого починали, дорівнює числу в останній комірці, то значить лінія коректна
if ((LineStack.len > 0) && (LineStack.len == LineStack.num) && 
(LineStack.num == GetNumberFromByte(jc->M[LineStack.stack[LineStack.len - 1].x][LineStack.stack[LineStack.len - 1].y]))
)
{
// Додаємо лінію в вектор скасування
SetMatrixElement(jc, LineStack.stack[0].x, LineStack.stack[0].y/*, 1*/);

// перемальовуємо поточний стек в матрицю М

// ознаки того, де знаходиться попередня/наступна клітинка [0 - зліва, 1 - праворуч, 2 - зверху, 3 - знизу]
char l_prev, l_next; 

// спочатку малюємо всю лінію, крім першої та останньої клітини
for (int i = 1; i < LineStack.len - 1; i++)
{
// визначаємо положення перед клітини
if (LineStack.stack[i - 1].x + 1 == LineStack.stack[i].x) l_prev = 0;
if (LineStack.stack[i - 1].x - 1 == LineStack.stack[i].x) l_prev = 1;
if (LineStack.stack[i - 1].y + 1 == LineStack.stack[i].y) l_prev = 2;
if (LineStack.stack[i - 1].y - 1 == LineStack.stack[i].y) l_prev = 3;

// визначаємо положення слід. клітини
if (LineStack.stack[i + 1].x + 1 == LineStack.stack[i].x) l_next = 0;
if (LineStack.stack[i + 1].x - 1 == LineStack.stack[i].x) l_next = 1;
if (LineStack.stack[i + 1].y + 1 == LineStack.stack[i].y) l_next = 2;
if (LineStack.stack[i + 1].y - 1 == LineStack.stack[i].y) l_next = 3;

l_prev = MAX(l_prev, l_next) * 10 + MIN(l_prev, l_next);

switch (l_prev)
{
case 32: jc->M[LineStack.stack[i].x][LineStack.stack[i].y] = 
jc->M[LineStack.stack[i].x][LineStack.stack[i].y] + LN_VERTICAL; break;
case 31: jc->M[LineStack.stack[i].x][LineStack.stack[i].y] = 
jc->M[LineStack.stack[i].x][LineStack.stack[i].y] + LN_LEFT_TOP; break;
case 30: jc->M[LineStack.stack[i].x][LineStack.stack[i].y] = 
jc->M[LineStack.stack[i].x][LineStack.stack[i].y] + LN_RIGHT_TOP; break;
case 21: jc->M[LineStack.stack[i].x][LineStack.stack[i].y] = 
jc->M[LineStack.stack[i].x][LineStack.stack[i].y] + LN_LEFT_BOTTOM; break;
case 20: jc->M[LineStack.stack[i].x][LineStack.stack[i].y] = 
jc->M[LineStack.stack[i].x][LineStack.stack[i].y] + LN_RIGHT_BOTTOM; break;
case 10: jc->M[LineStack.stack[i].x][LineStack.stack[i].y] = 
jc->M[LineStack.stack[i].x][LineStack.stack[i].y] + LN_HORIZONTAL; break;
}
}
// визначаємо для першої клітини 
if (LineStack.stack[1].x + 1 == LineStack.stack[0].x) 
jc->M[LineStack.stack[0].x][LineStack.stack[0].y] = jc->M[LineStack.stack[0].x][LineStack.stack[0].y] + LN_RIGHT;
if (LineStack.stack[1].x - 1 == LineStack.stack[0].x) 
jc->M[LineStack.stack[0].x][LineStack.stack[0].y] = jc->M[LineStack.stack[0].x][LineStack.stack[0].y] + LN_LEFT;
if (LineStack.stack[1].y + 1 == LineStack.stack[0].y) 
jc->M[LineStack.stack[0].x][LineStack.stack[0].y] = jc->M[LineStack.stack[0].x][LineStack.stack[0].y] + LN_BOTTOM;
if (LineStack.stack[1].y - 1 == LineStack.stack[0].y) 
jc->M[LineStack.stack[0].x][LineStack.stack[0].y] = jc->M[LineStack.stack[0].x][LineStack.stack[0].y] + LN_TOP;


// визначаємо для останньої клітини
if (LineStack.stack[LineStack.len - 2].x + 1 == LineStack.stack[LineStack.len - 1].x) 
jc->M[LineStack.stack[LineStack.len - 1].x][LineStack.stack[LineStack.len - 1].y] = 
jc->M[LineStack.stack[LineStack.len - 1].x][LineStack.stack[LineStack.len - 1].y] + LN_RIGHT;
if (LineStack.stack[LineStack.len - 2].x - 1 == LineStack.stack[LineStack.len - 1].x) 
jc->M[LineStack.stack[LineStack.len - 1].x][LineStack.stack[LineStack.len - 1].y] = 
jc->M[LineStack.stack[LineStack.len - 1].x][LineStack.stack[LineStack.len - 1].y] + LN_LEFT;
if (LineStack.stack[LineStack.len - 2].y + 1 == LineStack.stack[LineStack.len - 1].y) 
jc->M[LineStack.stack[LineStack.len - 1].x][LineStack.stack[LineStack.len - 1].y] = 
jc->M[LineStack.stack[LineStack.len - 1].x][LineStack.stack[LineStack.len - 1].y] + LN_BOTTOM;
if (LineStack.stack[LineStack.len - 2].y - 1 == LineStack.stack[LineStack.len - 1].y) 
jc->M[LineStack.stack[LineStack.len - 1].x][LineStack.stack[LineStack.len - 1].y] = 
jc->M[LineStack.stack[LineStack.len - 1].x][LineStack.stack[LineStack.len - 1].y] + LN_TOP;

DrawContext.need_save = true;
}
}


Отрисовка кросворду та поточної лінії
Побудова кросворду ділиться на наступні етапи:

  • Малювання зафарбованих клітинок матриці;
  • Малювання осередків стека поточної лінії;
  • Малювання ліній на зафарбованих клітинках матриці;
  • Малювання ліній на осередках стека поточної лінії;
  • Малювання чисел в осередках.
Щоб не надмірно не перевантажувати статтю кодом, наведемо лише лістинг з самого цікавого з вищевказаних пунктів.

Малювання ліній на зафарбованих клітинках матриці
if (jc->Resolved == false) // Якщо кросворд не вирішене
{
//Малюємо на клітинах з'єднувальні лінії
Iw2DSetColour(ColorSchema.JCCellLineColor);

for (int i = 0; i < jc->W; i++)
for (int j = 0; j < jc->H; j++)
{
n1 = jc->M[i][j] & BYTE_FLAG_PART;

switch (n1)
{
case LN_HORIZONTAL:
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i)* DrawContext.cell_wh + 0, 
DrawContext.jc_screen_y + (j)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1
), 
CIwFVec2(DrawContext.cell_wh - 0, 3)
);
break;

case LN_VERTICAL:
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1, 
DrawContext.jc_screen_y + (j)* DrawContext.cell_wh + 0), 
CIwFVec2(3, DrawContext.cell_wh - 0)
);
break;

case LN_RIGHT:
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i)* DrawContext.cell_wh + 0, 
DrawContext.jc_screen_y + (j)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1), 
CIwFVec2(4, 3)
);
break;

case LN_LEFT:
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i + 1)* DrawContext.cell_wh - 4, 
DrawContext.jc_screen_y + (j)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1), 
CIwFVec2(4, 3)
);
break;

case LN_BOTTOM:
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1, 
DrawContext.jc_screen_y + (j)* DrawContext.cell_wh + 0), 
CIwFVec2(3, 4)
);
break;

case LN_TOP:
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1, 
DrawContext.jc_screen_y + (j + 1)* DrawContext.cell_wh + 0 - 4), 
CIwFVec2(3, 4)
);
break;

case LN_RIGHT_BOTTOM:
// горизонтальна полулиния
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i)* DrawContext.cell_wh + 0, 
DrawContext.jc_screen_y + (j)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1), 
CIwFVec2(DrawContext.cell_wh / 2 - 1 + 3, 3)
);
// вертикальна полулиния 
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1, 
DrawContext.jc_screen_y + (j)* DrawContext.cell_wh + 0), 
CIwFVec2(3, DrawContext.cell_wh / 2 - 1 + 3)
);
break;

case LN_RIGHT_TOP:
// горизонтальна полулиния 
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i)* DrawContext.cell_wh + 0, 
DrawContext.jc_screen_y + (j)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1), 
CIwFVec2(DrawContext.cell_wh / 2 - 1 + 3, 3)
);
// вертикальна полулиния 
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1, 
DrawContext.jc_screen_y + (j)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1), 
CIwFVec2(3, DrawContext.cell_wh / 2 - 1 + 3)
);
break;

case LN_LEFT_BOTTOM:
// горизонтальна полулиния 
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1, 
DrawContext.jc_screen_y + (j)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1), 
CIwFVec2(DrawContext.cell_wh / 2 - 1 + 3, 3)
);
// вертикальна полулиния 
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1, 
DrawContext.jc_screen_y + (j)* DrawContext.cell_wh + 0), 
CIwFVec2(3, DrawContext.cell_wh / 2 - 1 + 3)
);
break;

case LN_LEFT_TOP:
// горизонтальна полулиния
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1, 
DrawContext.jc_screen_y + (j)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1), 
CIwFVec2(DrawContext.cell_wh / 2 - 1 + 3, 3)
);
// вертикальна полулиния
Iw2DFillRect(CIwFVec2(DrawContext.jc_screen_x + (i)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1, 
DrawContext.jc_screen_y + (j)* DrawContext.cell_wh + DrawContext.cell_wh / 2 - 1), 
CIwFVec2(3, DrawContext.cell_wh / 2 - 1 + 3)
);
break;
}
}
}


Перевірка вирішення кросворду
У зв'язку з тим, що коректність кожної намальованої лінії перевіряється в момент її малювання, всі існуючі лінії відповідають трьом правилами, наведеними на початку статті. А це в свою чергу означає, що для перевірки вирішення кросворду нам достатньо переконатися, що всі клітини з числами зафарбовані.

CheckJC
/* функція повертає 0, якщо кросворд вирішене, 
інакше координату першому некоректною клітинки,
не відповідає необхідному кінцевого рішення
*/
int CheckJC(JCStruct * p) 
{
for(int i = 0; i < p>W; i++)
for(int j = 0; j < p>H; j++) 
if(GetNumberFromByte(p->M[i][j]) > 1) // вважаємо, що цифра 1 завжди зафарбований за замовчуванням, з цього її не перевіряємо
if(GetFlagFromByte(p->M[i][j]) == 0) // якщо комірка не зафарбований
return 100 * (i + 1) + (j + 1);

return 0;
}


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.