new game/toggle fullscreen buttons
[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::randomize ()
118 {
119     FieldRect rect;
120     rect.flood = false;
121
122     data.clear();
123     data = RectVector(numRects[size] * numRects[size], rect);
124
125     qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
126
127     int numBrushes = ColorScheme::instance().getScheme().size();
128
129     for (QVector<FieldRect>::iterator rect = data.begin();
130          rect != data.end();
131          rect++)
132     {
133         (*rect).brush = qrand() % numBrushes;
134     }
135
136     turns = 0;
137     finished = false;
138     emit turnsChanged(turns);
139
140     // flood from top-left
141     data[0].flood = true;
142     floodNeighbours(data[0].brush, 0, 0);
143
144     update();
145 }
146
147 int Field::getNumRectsOfSize (FieldSize size)
148 {
149     return numRects[size];
150 }
151
152 int Field::getNumTurnsOfSize (FieldSize size)
153 {
154     return numTurns[size];
155 }
156
157 int Field::getRectSize (FieldSize size)
158 {
159     return fieldWidth / numRects[size];
160 }
161
162 void Field::tryFloodRecurse (quint8 brush, int x, int y)
163 {
164     FieldRect &rect = data[x + y*numRects[size]];
165
166     if (!rect.flood && rect.brush == brush)
167     {
168         rect.flood = true;
169         floodNeighbours(brush, x, y);
170     }
171 }
172
173 void Field::floodNeighbours (quint8 brush, int x, int y)
174 {
175     int s = numRects[size];
176
177     data[x + y*s].brush = brush;
178
179     if (x > 0)
180         tryFloodRecurse(brush, x - 1, y);
181
182     if (y > 0)
183         tryFloodRecurse(brush, x, y - 1);
184
185     if (x < s - 1)
186         tryFloodRecurse(brush, x + 1, y);
187         
188     if (y < s - 1)
189         tryFloodRecurse(brush, x, y + 1);
190 }
191
192 void Field::paintEvent (QPaintEvent *event)
193 {
194     QPainter painter;
195     painter.begin(this);
196
197     QRect rect = QRect(0, 0, getRectSize(size), getRectSize(size));
198
199     const QVector<QBrush> &scheme = ColorScheme::instance().getScheme();
200
201     for (int y = 0; y < numRects[size] ;y++)
202     {
203         int n = y * numRects[size];
204
205         for (int x = 0; x < numRects[size] ;x++, n++)
206         {
207             rect.moveTo(x * rect.width(), y * rect.height());
208
209             if (rect.intersects(event->rect()))
210                 painter.fillRect(rect, scheme.at(data[n].brush));
211         }
212     }
213
214     painter.end();
215 }
216
217 void Field::flood (int colorIndex)
218 {
219     // don't fill with the same color over and over again
220     if (colorIndex == data[0].brush)
221         return;
222
223     if (finished)
224         return;
225
226     emit turnsChanged(++turns);
227
228     // flood with new color
229     for (int y = 0; y < numRects[size] ;y++)
230     {
231         int n = y * numRects[size];
232
233         for (int x = 0; x < numRects[size] ;x++, n++)
234         {
235             if (data[n].flood)
236                 floodNeighbours(colorIndex, x, y);
237         }
238     }
239
240     repaint();
241
242     bool allFlooded = true;
243
244     // check if all field flooded
245     for (QVector<Field::FieldRect>::const_iterator rect = data.begin();
246          rect != data.end();
247          rect++)
248     {
249         if (!(*rect).flood)
250         {
251             allFlooded = false;
252             break;
253         }
254     }
255
256     QString msg;
257
258     if (allFlooded)
259     {
260         finished = true;
261         /*: win message */
262         msg = tr("You won!");
263     }
264     else if (getNumTurnsOfSize(size) == turns)
265     {
266         finished = true;
267         /*: fail message */
268         msg = tr("You lost!");
269     }
270
271     if (finished)
272     {
273         QMessageBox box;
274         box.setText(msg);
275         box.exec();
276         randomize();
277     }
278 }