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