1 /*
2 SPDX-FileCopyrightText: 2009-2021 Graeme Gott <graeme@gottcode.org>
3
4 SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7 #include "piece.h"
8
9 #include "puzzle.h"
10
11 #include <QApplication>
12 #include <QGraphicsItemAnimation>
13 #include <QGraphicsScene>
14 #include <QGraphicsSceneMouseEvent>
15 #include <QPen>
16 #include <QPointF>
17 #include <QPolygonF>
18 #include <QRadialGradient>
19 #include <QTimeLine>
20 #include <QTimer>
21
22 //-----------------------------------------------------------------------------
23
Piece(Puzzle * puzzle)24 Piece::Piece(Puzzle* puzzle)
25 : QGraphicsEllipseItem(0, 0, 100, 100)
26 , m_puzzle(puzzle)
27 , m_gradient(50, 50, 90)
28 , m_rotations(0)
29 , m_swap_piece(nullptr)
30 , m_clicked(false)
31 , m_rotate_clockwise(true)
32 {
33 for (int i = 0; i < 6; ++i) {
34 m_colors.append(i);
35 m_connectors.append(-1);
36 }
37 setFlag(ItemIsMovable, true);
38 setHighlight(false);
39
40 for (int i = 0; i < 24; ++i) {
41 m_gradient.setColorAt(static_cast<float>(i) / 24.0f, Qt::black);
42 }
43
44 m_rotate_timer = new QTimer(this);
45 connect(m_rotate_timer, &QTimer::timeout, this, &Piece::rotateConnectors);
46 m_rotate_timer->setInterval(40);
47
48 m_puzzle->addItem(this);
49
50 // Create middle of piece
51 QGraphicsEllipseItem* overlay = new QGraphicsEllipseItem(0, 0, 100, 100, this);
52 overlay->setPen(Qt::NoPen);
53 QRadialGradient gradient(50, 50, 50);
54 gradient.setColorAt(0.05, Qt::black);
55 gradient.setColorAt(0.15, Qt::transparent);
56 overlay->setBrush(gradient);
57 }
58
59 //-----------------------------------------------------------------------------
60
setConnector(int offset,int value)61 bool Piece::setConnector(int offset, int value)
62 {
63 if (m_colors.contains(value)) {
64 m_connectors[offset] = value;
65 m_colors.removeOne(value);
66
67 static QColor connector_colors[6] = {
68 QColor(255, 0, 0),
69 QColor(255, 255, 0),
70 QColor(0, 255, 0),
71 QColor(0, 255, 255),
72 QColor(0, 0, 255),
73 QColor(255, 0, 255)
74 };
75 m_gradient.setColorAt(static_cast<float>(6 - offset) / 6.0f, connector_colors[value]);
76 if (offset == 0) {
77 m_gradient.setColorAt(0, connector_colors[value]);
78 }
79 setBrush(m_gradient);
80
81 return true;
82 } else {
83 return false;
84 }
85 }
86
87 //-----------------------------------------------------------------------------
88
setHighlight(bool highlight)89 void Piece::setHighlight(bool highlight)
90 {
91 setPen(QPen(highlight ? Qt::white : Qt::darkGray, 2));
92 setZValue(highlight ? 2 : 1);
93 }
94
95 //-----------------------------------------------------------------------------
96
setPosition(const QPointF & position)97 void Piece::setPosition(const QPointF& position)
98 {
99 m_position = position;
100 setPos(m_position);
101 }
102
103 //-----------------------------------------------------------------------------
104
spin(int rotations)105 void Piece::spin(int rotations)
106 {
107 int angle = 90;
108 for (int i = 0; i < rotations; ++i) {
109 m_connectors.move(5, 0);
110 angle -= 60;
111 }
112 if (angle < 0) {
113 angle += 360;
114 }
115 m_gradient.setAngle(angle);
116 setBrush(m_gradient);
117 }
118
119 //-----------------------------------------------------------------------------
120
fromString(const QString & string)121 bool Piece::fromString(const QString& string)
122 {
123 #if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
124 const QStringList values = string.split(",", Qt::SkipEmptyParts);
125 #else
126 const QStringList values = string.split(",", QString::SkipEmptyParts);
127 #endif
128 if (values.count() != 6) {
129 return false;
130 }
131
132 for (int i = 0; i < 6; ++i) {
133 if (!setConnector(i, values[i].toInt())) {
134 return false;
135 }
136 }
137 return true;
138 }
139
140 //-----------------------------------------------------------------------------
141
toString() const142 QString Piece::toString() const
143 {
144 QStringList values;
145 for (int i = 0; i < 6; ++i) {
146 values.append(QString::number(m_connectors[i]));
147 }
148 return values.join(",");
149 }
150
151 //-----------------------------------------------------------------------------
152
mouseMoveEvent(QGraphicsSceneMouseEvent * event)153 void Piece::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
154 {
155 // Do not rotate because piece was dragged
156 if ((m_start_position - event->screenPos()).manhattanLength() >= QApplication::startDragDistance()) {
157 m_clicked = false;
158 }
159
160 // Find piece containing center
161 Piece* swap_piece = nullptr;
162 const QList<QGraphicsItem*> items = scene()->items(sceneBoundingRect().center());
163 for (QGraphicsItem* item : items) {
164 Piece* piece = qgraphicsitem_cast<Piece*>(item);
165 if (piece && piece != this) {
166 swap_piece = piece;
167 break;
168 }
169 }
170
171 // Highlight swap piece
172 if (swap_piece != m_swap_piece) {
173 if (m_swap_piece) {
174 m_swap_piece->setHighlight(false);
175 }
176 m_swap_piece = swap_piece;
177 if (m_swap_piece) {
178 m_swap_piece->setHighlight(true);
179 }
180 }
181
182 QGraphicsEllipseItem::mouseMoveEvent(event);
183 }
184
185 //-----------------------------------------------------------------------------
186
mousePressEvent(QGraphicsSceneMouseEvent * event)187 void Piece::mousePressEvent(QGraphicsSceneMouseEvent* event)
188 {
189 if (event->button() == Qt::LeftButton) {
190 setZValue(3);
191 m_swap_piece = nullptr;
192 }
193 m_start_position = event->screenPos();
194 m_clicked = true;
195 m_rotate_clockwise = event->button() != Qt::RightButton;
196 QGraphicsEllipseItem::mousePressEvent(event);
197 }
198
199 //-----------------------------------------------------------------------------
200
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)201 void Piece::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
202 {
203 if (m_swap_piece) {
204 m_puzzle->swapPieces(this, m_swap_piece);
205 qSwap(m_position, m_swap_piece->m_position);
206 m_swap_piece->setHighlight(false);
207 m_swap_piece->setZValue(2);
208 m_swap_piece->moveTo(m_swap_piece->m_position);
209 m_swap_piece = nullptr;
210 } else if (m_clicked) {
211 rotate();
212 m_clicked = false;
213 }
214 moveTo(m_position);
215 QGraphicsEllipseItem::mouseReleaseEvent(event);
216 }
217
218 //-----------------------------------------------------------------------------
219
rotateConnectors()220 void Piece::rotateConnectors()
221 {
222 float angle = m_gradient.angle();
223 if (m_rotate_clockwise) {
224 angle -= 20;
225 if (angle < 0) {
226 angle += 360;
227 }
228 } else {
229 angle += 20;
230 if (angle > 360) {
231 angle -= 360;
232 }
233 }
234 m_gradient.setAngle(angle);
235 setBrush(m_gradient);
236
237 --m_rotations;
238 if (m_rotations == 0) {
239 m_rotate_timer->stop();
240 actionFinished();
241 }
242 }
243
244 //-----------------------------------------------------------------------------
245
actionFinished()246 void Piece::actionFinished()
247 {
248 setZValue(1);
249 if (!m_puzzle->isDone()) {
250 setFlag(ItemIsMovable, true);
251 }
252 }
253
254 //-----------------------------------------------------------------------------
255
moveTo(const QPointF & new_pos)256 void Piece::moveTo(const QPointF& new_pos)
257 {
258 setFlag(ItemIsMovable, false);
259
260 QTimeLine* timeline = new QTimeLine(200, this);
261
262 QGraphicsItemAnimation* animation = new QGraphicsItemAnimation(this);
263 animation->setItem(this);
264 animation->setTimeLine(timeline);
265 animation->setPosAt(0, pos());
266 animation->setPosAt(1, new_pos);
267
268 connect(timeline, &QTimeLine::finished, this, &Piece::actionFinished);
269 connect(timeline, &QTimeLine::finished, animation, &QGraphicsItemAnimation::deleteLater);
270 connect(timeline, &QTimeLine::finished, timeline, &QTimeLine::deleteLater);
271 timeline->start();
272 }
273
274 //-----------------------------------------------------------------------------
275
rotate()276 void Piece::rotate()
277 {
278 m_rotations += 3;
279 if (m_rotate_clockwise) {
280 m_connectors.move(5, 0);
281 } else {
282 m_connectors.move(0, 5);
283 }
284 if (!m_rotate_timer->isActive()) {
285 m_rotate_timer->start();
286 setFlag(ItemIsMovable, false);
287 }
288 }
289
290 //-----------------------------------------------------------------------------
291