7304540a28dd0f2ec63d54a2595fdabc88cdea5f
[colorflood] / colorflood / src / field.cpp
1 /*
2   Copyright 2010 Serge Ziryukin <ftrvxmtrx@gmail.com>
3
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation; version 2 of the License.
7
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11   GNU General Public License for more details.
12 */
13
14 #include <QMetaType>
15 #include <QDataStream>
16 #include <QSettings>
17 #include <QTime>
18 #include <QPainter>
19 #include <QPaintEvent>
20 #include <QMessageBox>
21 #include "field.hpp"
22 #include "colorscheme.hpp"
23
24 static const int fieldWidth = 420;
25
26 const int Field::numRects[Field::NUM_SIZES] = { 14, 21, 28 };
27 const int Field::numTurns[Field::NUM_SIZES] = { 25, 35, 50 };
28
29 // we declare out QVector<FieldRect> metatype
30 // and stream operators to save whole field to settings
31 Q_DECLARE_METATYPE(Field::RectVector);
32
33 static QDataStream &operator<< (QDataStream &out, const Field::RectVector &rv)
34 {
35     for (QVector<Field::FieldRect>::const_iterator rect = rv.begin();
36          rect != rv.end();
37          rect++)
38     {
39         out << (*rect).brush;
40         out << (*rect).flood;
41     }
42
43     return out;
44 }
45
46 static QDataStream &operator>> (QDataStream &in, Field::RectVector &rv)
47 {
48     Field::FieldRect r;
49
50     rv.clear();
51
52     for (; !in.atEnd() ;)
53     {
54         in >> r.brush >> r.flood;
55         rv << r;
56     }
57
58     rv.pop_back();
59
60     return in;
61 }
62
63 Field::Field (QWidget *parent, int *turns)
64     : QWidget (parent),
65       finished(false)
66 {
67     Q_ASSERT(parent);
68
69     setFixedSize(fieldWidth, fieldWidth);
70
71     // restore field size and field itself from settings
72
73     qRegisterMetaType<RectVector>("Field::RectVector");
74     qRegisterMetaTypeStreamOperators<RectVector>("Field::RectVector");
75
76     QSettings settings;
77
78     int size = settings.value("field/size", SIZE_SMALL).toInt();
79
80     if (size < SIZE_SMALL || size >= NUM_SIZES)
81         size = SIZE_SMALL;
82
83     this->size = (FieldSize)size;
84
85     if (settings.contains("field/data"))
86         data = settings.value("field/data").value<RectVector>();
87
88     this->turns = settings.value("field/turns", 0).toInt();
89
90     if (data.size() != numRects[size] * numRects[size])
91         randomize();
92
93     *turns = this->turns;
94 }
95
96 Field::~Field ()
97 {
98     if (!finished)
99     {
100         QSettings settings;
101
102         settings.setValue("field/size", size);
103
104         QVariant v;
105         v.setValue(data);
106         settings.setValue("field/data", v);
107
108         settings.setValue("field/turns", turns);
109     }
110 }
111
112 Field::FieldSize Field::getSize () const
113 {
114     return size;
115 }
116
117 void Field::setSize (int size)
118 {
119     Q_ASSERT(size >= 0 && size < NUM_SIZES);
120
121     if (this->size == size)
122         return;
123
124     this->size = (FieldSize)size;
125     randomize();
126 }
127
128 void Field::randomize ()
129 {
130     FieldRect rect;
131     rect.flood = false;
132
133     data.clear();
134     data = RectVector(numRects[size] * numRects[size], rect);
135
136     qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
137
138     int numBrushes = ColorScheme::instance().getScheme().size();
139
140     for (QVector<FieldRect>::iterator rect = data.begin();
141          rect != data.end();
142          rect++)
143     {
144         (*rect).brush = qrand() % numBrushes;
145     }
146
147     turns = 0;
148     finished = false;
149     emit turnsChanged(turns);
150
151     // flood from top-left
152     data[0].flood = true;
153     floodNeighbours(data[0].brush, 0, 0);
154
155     update();
156 }
157
158 int Field::getNumRectsOfSize (FieldSize size)
159 {
160     return numRects[size];
161 }
162
163 int Field::getNumTurnsOfSize (FieldSize size)
164 {
165     return numTurns[size];
166 }
167
168 int Field::getRectSize (FieldSize size)
169 {
170     return fieldWidth / numRects[size];
171 }
172
173 void Field::tryFloodRecurse (quint8 brush, int x, int y)
174 {
175     FieldRect &rect = data[x + y*numRects[size]];
176
177     if (!rect.flood && rect.brush == brush)
178     {
179         rect.flood = true;
180         floodNeighbours(brush, x, y);
181     }
182 }
183
184 void Field::floodNeighbours (quint8 brush, int x, int y)
185 {
186     int s = numRects[size];
187
188     data[x + y*s].brush = brush;
189
190     if (x > 0)
191         tryFloodRecurse(brush, x - 1, y);
192
193     if (y > 0)
194         tryFloodRecurse(brush, x, y - 1);
195
196     if (x < s - 1)
197         tryFloodRecurse(brush, x + 1, y);
198         
199     if (y < s - 1)
200         tryFloodRecurse(brush, x, y + 1);
201 }
202
203 void Field::paintEvent (QPaintEvent *event)
204 {
205     QPainter painter;
206     painter.begin(this);
207
208     QRect rect = QRect(0, 0, getRectSize(size), getRectSize(size));
209
210     const QVector<QBrush> &scheme = ColorScheme::instance().getScheme();
211
212     for (int y = 0; y < numRects[size] ;y++)
213     {
214         int n = y * numRects[size];
215
216         for (int x = 0; x < numRects[size] ;x++, n++)
217         {
218             rect.moveTo(x * rect.width(), y * rect.height());
219
220             if (rect.intersects(event->rect()))
221                 painter.fillRect(rect, scheme.at(data[n].brush));
222         }
223     }
224
225     painter.end();
226 }
227
228 void Field::flood (int colorIndex)
229 {
230     // don't fill with the same color over and over again
231     if (colorIndex == data[0].brush)
232         return;
233
234     if (finished)
235         return;
236
237     emit turnsChanged(++turns);
238
239     // flood with new color
240     for (int y = 0; y < numRects[size] ;y++)
241     {
242         int n = y * numRects[size];
243
244         for (int x = 0; x < numRects[size] ;x++, n++)
245         {
246             if (data[n].flood)
247                 floodNeighbours(colorIndex, x, y);
248         }
249     }
250
251     repaint();
252
253     bool allFlooded = true;
254
255     // check if all field flooded
256     for (QVector<Field::FieldRect>::const_iterator rect = data.begin();
257          rect != data.end();
258          rect++)
259     {
260         if (!(*rect).flood)
261         {
262             allFlooded = false;
263             break;
264         }
265     }
266
267     QString msg;
268
269     if (allFlooded)
270     {
271         finished = true;
272         /*: win message */
273         msg = tr("You won!");
274     }
275     else if (getNumTurnsOfSize(size) == turns)
276     {
277         finished = true;
278         /*: fail message */
279         msg = tr("You lost!");
280     }
281
282     if (finished)
283     {
284         QMessageBox box;
285         box.setWindowTitle("Color Flood");
286         box.setText(msg);
287         box.exec();
288         randomize();
289     }
290 }