File indexing completed on 2024-10-13 03:44:24
0001 /* 0002 SPDX-FileCopyrightText: 2004-2005 Andi Peredri <andi@ukr.net> 0003 SPDX-FileCopyrightText: 2007-2008 Fela Winkelmolen <fela.kde@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "abstractgrid.h" 0009 0010 #include <QMap> 0011 #include <QString> 0012 #include <QDebug> 0013 #include <QRandomGenerator> 0014 0015 AbstractCell::AbstractCell(int index) 0016 : m_index(index) 0017 { 0018 makeEmpty(); 0019 } 0020 0021 char *AbstractCell::toString() { 0022 char *str = new char[4]; 0023 str[0] = (m_cables & Left) ? '-' : ' ' ; 0024 str[2] = (m_cables & Right) ? '-' : ' ' ; 0025 if ((m_cables & Up) && (m_cables & Down)) { 0026 str[1] = '|'; 0027 } else if (m_cables & Up) { 0028 str[1] = '\''; 0029 } else if (m_cables & Down) { 0030 str[1] = ','; 0031 } else if ((m_cables & Left) && (m_cables & Right)){ 0032 str[1] = '-'; 0033 } else { 0034 str[1] = ' '; 0035 } 0036 str[3] = '\0'; 0037 0038 return str; 0039 } 0040 0041 bool AbstractCell::isTerminal() const 0042 { 0043 if (m_isServer){ 0044 return false; 0045 } 0046 return (m_cables == Up || m_cables == Right 0047 || m_cables == Down || m_cables == Left); 0048 } 0049 0050 // only used to change the cables (not to rotate the cell!) 0051 void AbstractCell::setCables(Directions newCables) 0052 { 0053 m_cables = originalCables = newCables; 0054 m_hasBeenMoved = false; 0055 } 0056 0057 void AbstractCell::setServer(bool isServer) 0058 { 0059 m_isServer = isServer; 0060 } 0061 0062 void AbstractCell::setConnected(bool isConnected) 0063 { 0064 m_isConnected = isConnected; 0065 } 0066 0067 void AbstractCell::makeEmpty() 0068 { 0069 m_cables = originalCables = None; 0070 m_isServer = false; 0071 m_isConnected = false; 0072 m_hasBeenMoved = false; 0073 } 0074 0075 void AbstractCell::emptyMove() 0076 { 0077 m_hasBeenMoved = true; 0078 } 0079 0080 void AbstractCell::rotateClockwise() 0081 { 0082 Directions newdirs = None; 0083 if (m_cables & Up) newdirs = Directions(newdirs | Right); 0084 if (m_cables & Right) newdirs = Directions(newdirs | Down); 0085 if (m_cables & Down) newdirs = Directions(newdirs | Left); 0086 if (m_cables & Left) newdirs = Directions(newdirs | Up); 0087 m_cables = newdirs; 0088 m_hasBeenMoved = true; 0089 } 0090 0091 void AbstractCell::rotateCounterclockwise() 0092 { 0093 Directions newdirs = None; 0094 if (m_cables & Up) newdirs = Directions(newdirs | Left); 0095 if (m_cables & Right) newdirs = Directions(newdirs | Up); 0096 if (m_cables & Down) newdirs = Directions(newdirs | Right); 0097 if (m_cables & Left) newdirs = Directions(newdirs | Down); 0098 m_cables = newdirs; 0099 m_hasBeenMoved = true; 0100 } 0101 0102 void AbstractCell::invert() 0103 { 0104 rotateClockwise(); 0105 rotateClockwise(); 0106 } 0107 0108 void AbstractCell::reset() 0109 { 0110 m_cables = originalCables; 0111 m_hasBeenMoved = false; 0112 } 0113 0114 0115 0116 AbstractGrid::AbstractGrid() 0117 : m_width(0), m_height(0) 0118 { 0119 } 0120 0121 AbstractGrid::~AbstractGrid() 0122 { 0123 qDeleteAll(m_cells); 0124 } 0125 0126 void AbstractGrid::initializeGrid(uint width, uint height, Wrapping wrapping) 0127 { 0128 if ((width * height) != (m_width * m_height)) { 0129 qDeleteAll(m_cells); 0130 m_cells.clear(); 0131 0132 for (uint index = 0; index < width*height; ++index) { 0133 m_cells.append(newCell(index)); 0134 } 0135 } 0136 0137 m_width = width; 0138 m_height = height; 0139 m_isWrapped = wrapping; 0140 0141 createGrid(); 0142 0143 while(hasUnneededCables() || solutionCount() != 1) { 0144 // the grid is invalid: create a new one 0145 createGrid(); 0146 } 0147 0148 m_minimumMoves = 0; 0149 const int shuffleLimit = cellCount() * minCellRatio; 0150 QList<int> notShuffledCells; 0151 for (int i = 0; i < cellCount(); ++i) 0152 notShuffledCells.append(i); 0153 0154 // select a random cell that is not yet shuffled 0155 // rotate such that initial and final states are not same 0156 // repeat above two steps until minimum moves equal to shuffle limit 0157 auto *generator = QRandomGenerator::global(); 0158 while(m_minimumMoves < shuffleLimit) 0159 { 0160 // selecting a random index 0161 int index = generator->bounded(notShuffledCells.count()); 0162 int cellNo = notShuffledCells[index]; 0163 // removing the selected index so that it must not be used again 0164 notShuffledCells.removeAt(index); 0165 AbstractCell *cell = m_cells[cellNo]; 0166 Directions dir = cell->cables(); 0167 0168 // excludes None(Empty cell) 0169 if (dir == None) { 0170 continue; 0171 } 0172 // if straight line rotate once 0173 // cant rotate twice(it will be back on its initial state) 0174 else if ((dir == (Up | Down)) || (dir == (Left | Right))) { 0175 m_minimumMoves += 1; 0176 cell->rotateClockwise(); 0177 } 0178 // for every other case rotate 1..3 times 0179 else { 0180 int rotation = generator->bounded(3) + 1; // 1..3 0181 // cant rotate twice when m_minimumMoves == shuffleLimit - 1 0182 if (m_minimumMoves == shuffleLimit - 1 && rotation == 2){ 0183 rotation = (generator->bounded(2))? 1 : 3; // 1 or 3 0184 } 0185 m_minimumMoves += (rotation == 3) ? 1 : rotation; 0186 while(rotation--) { 0187 cell->rotateClockwise(); 0188 } 0189 } 0190 } 0191 0192 updateConnections(); 0193 } 0194 0195 void AbstractGrid::print() { 0196 //system("clear"); 0197 QString str1; 0198 QString str2; 0199 int index = 0; 0200 for (uint r = 0; r < m_height; ++r) { 0201 for (uint c = 0; c < m_width; ++c) { 0202 str1 +=QLatin1String( m_cells[index]->toString() ); 0203 str1 += QLatin1String( " " ); 0204 if (m_cells[index]->hasBeenMoved()) { 0205 str2 += QLatin1String( "M " ); 0206 } else { 0207 str2 += QLatin1String( " " ); 0208 } 0209 ++index; 0210 } 0211 qDebug() << str1 << " " << str2; 0212 qDebug() << QLatin1String( " " ); 0213 str1 = str2 = QLatin1String( "" ); 0214 } 0215 } 0216 0217 // ============ auxiliary funciontions ===================== // 0218 0219 void AbstractGrid::createGrid() 0220 { 0221 // add a random server 0222 auto *generator = QRandomGenerator::global(); 0223 server_index = generator->bounded(cellCount()); 0224 0225 // number of cells that aren't free 0226 int notFreeCells = 0; 0227 const int minimumNumCells = cellCount() * minCellRatio; 0228 // retries until the minimum number of cells is big enough 0229 while (notFreeCells < minimumNumCells) { 0230 0231 for (int i = 0; i < cellCount(); ++i) { 0232 m_cells[i]->makeEmpty(); 0233 } 0234 m_cells[server_index]->setServer(true); 0235 0236 QList<uint> list; 0237 list.append(server_index); 0238 if (generator->bounded(2)) addRandomCable(list); 0239 0240 // add some random cables... 0241 // the list empties if there aren't many free cells left 0242 // (because of addRandomCable() not doing anything) 0243 while (!list.isEmpty()) { 0244 if (generator->bounded(2)) { 0245 addRandomCable(list); 0246 if (generator->bounded(2)) addRandomCable(list); 0247 list.erase(list.begin()); 0248 } 0249 else { 0250 list.append(list.first()); 0251 list.erase(list.begin()); 0252 } 0253 } 0254 0255 // count not empty cells 0256 notFreeCells = 0; 0257 for (int i = 0; i < cellCount(); ++i) { 0258 if (m_cells[i]->cables() != None) ++notFreeCells; 0259 } 0260 } 0261 } 0262 0263 // adds a random direction and moves on (if possible) 0264 void AbstractGrid::addRandomCable(QList<uint>& list) 0265 { 0266 int cell = list.first(); 0267 // find all the cells surrounding list.first() 0268 // (0 when cells don't exist) 0269 int ucell = uCell(cell); // up 0270 int rcell = rCell(cell); // right 0271 int dcell = dCell(cell); // down 0272 int lcell = lCell(cell); // left 0273 0274 QMap<Directions, int> freeCells; 0275 0276 // of those cells map the ones that are free 0277 if (ucell != NO_CELL && m_cells[ucell]->cables() == None) { 0278 freeCells[Up] = ucell; 0279 } 0280 if (rcell != NO_CELL && m_cells[rcell]->cables() == None) { 0281 freeCells[Right] = rcell; 0282 } 0283 if (dcell != NO_CELL && m_cells[dcell]->cables() == None) { 0284 freeCells[Down] = dcell; 0285 } 0286 if (lcell != NO_CELL && m_cells[lcell]->cables() == None) { 0287 freeCells[Left] = lcell; 0288 } 0289 0290 if (freeCells.isEmpty()) return; // no free cells left 0291 0292 QMap<Directions, int>::ConstIterator it = freeCells.constBegin(); 0293 // move the iterator to a random direction connecting to a free cell 0294 for (int i = QRandomGenerator::global()->bounded(freeCells.count()); i > 0; --i) ++it; 0295 0296 // add the cable in the direction of cell 0297 Directions newCables = Directions(m_cells[cell]->cables() | it.key()); 0298 m_cells[cell]->setCables(newCables); 0299 // add a cable in the opposite direction, on the free cell 0300 m_cells[it.value()]->setCables(invertDirection(it.key())); 0301 // append the cell that was free to the list 0302 list.append(it.value()); 0303 } 0304 0305 int AbstractGrid::uCell(uint cell) const 0306 { 0307 if (cell >= m_width) { 0308 return cell - m_width; 0309 } else if (m_isWrapped) { 0310 return m_width * (m_height - 1) + cell; 0311 } else { 0312 return NO_CELL; 0313 } 0314 } 0315 0316 int AbstractGrid::dCell(uint cell) const 0317 { 0318 if (cell < m_width * (m_height - 1)) { 0319 return cell + m_width; 0320 } else if (m_isWrapped) { 0321 return cell - m_width * (m_height - 1); 0322 } else { 0323 return NO_CELL; 0324 } 0325 } 0326 0327 int AbstractGrid::lCell(uint cell) const 0328 { 0329 if (cell % m_width > 0) { 0330 return cell - 1; 0331 } else if (m_isWrapped) { 0332 return cell - 1 + m_width; 0333 } else { 0334 return NO_CELL; 0335 } 0336 } 0337 0338 int AbstractGrid::rCell(uint cell) const 0339 { 0340 if (cell % m_width < m_width - 1) { 0341 return cell + 1; 0342 } else if (m_isWrapped) { 0343 return cell + 1 - m_width; 0344 } else { 0345 return NO_CELL; 0346 } 0347 } 0348 0349 // TODO: something better to invert directions (remove duplication etc...) 0350 Directions AbstractGrid::invertDirection(Directions givenDirection) 0351 { 0352 QMap<Directions, Directions> invDirs; 0353 invDirs[Up] = Down; 0354 invDirs[Right] = Left; 0355 invDirs[Down] = Up; 0356 invDirs[Left] = Right; 0357 0358 return invDirs[givenDirection]; 0359 } 0360 0361 0362 int AbstractGrid::solutionCount() 0363 { 0364 MoveList possibleNextMoves; 0365 // TODO: put following in external function 0366 for (AbstractCell *cell : std::as_const(m_cells)) { 0367 if (!cell->hasBeenMoved()) { 0368 Directions dirs = cell->cables(); 0369 Move move; 0370 if (dirs == None) { 0371 // no cables 0372 move = Move(cell->index(), Move::None); 0373 possibleNextMoves.append(move); 0374 } else if (dirs == (Up | Down) || dirs == (Left | Right)) { 0375 // cables forming a line 0376 move = Move(cell->index(), Move::None); 0377 possibleNextMoves.append(move); 0378 0379 move = Move(cell->index(), Move::Left); 0380 possibleNextMoves.append(move); 0381 } else { 0382 // other kind of cables 0383 move = Move(cell->index(), Move::None); 0384 possibleNextMoves.append(move); 0385 0386 move = Move(cell->index(), Move::Left); 0387 possibleNextMoves.append(move); 0388 0389 move = Move(cell->index(), Move::Right); 0390 possibleNextMoves.append(move); 0391 0392 move = Move(cell->index(), Move::Inverted); 0393 possibleNextMoves.append(move); 0394 } 0395 break; 0396 } 0397 } 0398 0399 // all cells have been moved 0400 if (possibleNextMoves.isEmpty()) { 0401 return isPossibleSolution() ? 1 : 0; 0402 } 0403 // else 0404 0405 int solutionsFound = 0; 0406 for (const Move &nextMove : std::as_const(possibleNextMoves)) { 0407 int index = nextMove.index(); 0408 0409 switch (nextMove.move()) { 0410 case Move::None: 0411 m_cells[index]->emptyMove(); 0412 break; 0413 case Move::Right: 0414 m_cells[index]->rotateClockwise(); 0415 break; 0416 case Move::Left: 0417 m_cells[index]->rotateCounterclockwise(); 0418 break; 0419 case Move::Inverted: 0420 m_cells[index]->invert(); 0421 break; 0422 } 0423 0424 if (movesDoneArePossible()) { 0425 solutionsFound += solutionCount(); // recursive call 0426 } 0427 0428 m_cells[index]->reset(); // undo move 0429 } 0430 return solutionsFound; 0431 } 0432 0433 bool AbstractGrid::movesDoneArePossible() 0434 { 0435 0436 for (AbstractCell *cell : std::as_const(m_cells)) { 0437 if (!cell->hasBeenMoved()) continue; 0438 0439 uint x = cell->index() % m_width; 0440 uint y = cell->index() / m_width; 0441 Directions cables = cell->cables(); 0442 0443 // check if there are moved cells near the borders that are wrong 0444 if (!m_isWrapped) { 0445 if (x == 0 && cables & Left) return false; 0446 if (x == m_width-1 && cables & Right) return false; 0447 if (y == 0 && cables & Up) return false; 0448 if (y == m_height-1 && cables & Down) return false; 0449 } 0450 0451 // check if there are contiguous moved cells that are wrong 0452 0453 if (cables & Left) { 0454 int lcell = lCell(cell->index()); 0455 if (lcell != NO_CELL && m_cells[lcell]->hasBeenMoved()) { 0456 // also the cell to the left of the current has been moved 0457 0458 // if it doesn't connect return false 0459 if (!(m_cells[lcell]->cables() & Right)) return false; 0460 } 0461 } 0462 if (cables & Right) { 0463 int rcell = rCell(cell->index()); 0464 if (rcell != NO_CELL && m_cells[rcell]->hasBeenMoved()) { 0465 if (!(m_cells[rcell]->cables() & Left)) return false; 0466 } 0467 } 0468 if (cables & Up) { 0469 int ucell = uCell(cell->index()); 0470 if (ucell != NO_CELL && m_cells[ucell]->hasBeenMoved()) { 0471 if (!(m_cells[ucell]->cables() & Down)) return false; 0472 } 0473 } 0474 if (cables & Down) { 0475 int dcell = dCell(cell->index()); 0476 if (dcell != NO_CELL && m_cells[dcell]->hasBeenMoved()) { 0477 if (!(m_cells[dcell]->cables() & Up)) return false; 0478 } 0479 } 0480 } 0481 0482 // nothing was wrong 0483 return true; 0484 } 0485 0486 bool AbstractGrid::hasUnneededCables() 0487 { 0488 for (AbstractCell *cell : std::as_const(m_cells)) { 0489 if (cell->isTerminal() || cell->isServer() || cell->cables() == None) { 0490 continue; 0491 } 0492 0493 Directions oldCables = cell->cables(); 0494 cell->setCables(None); 0495 0496 bool solution = isPossibleSolution(); 0497 cell->setCables(oldCables); 0498 0499 if (solution) { 0500 // it has a solution also when the cables of cell are removed 0501 return true; 0502 } 0503 } 0504 0505 return false; 0506 } 0507 0508 bool AbstractGrid::isPossibleSolution() 0509 { 0510 for (AbstractCell *cell : std::as_const(m_cells)) { 0511 cell->setConnected(false); 0512 } 0513 updateConnections(); 0514 0515 return allTerminalsConnected(); 0516 } 0517 0518 bool AbstractGrid::allTerminalsConnected() { 0519 // return false if there is a terminal that isn't connected 0520 for (AbstractCell *cell : std::as_const(m_cells)) { 0521 if (cell->isTerminal() && !cell->isConnected()) { 0522 return false; 0523 } 0524 } 0525 // all terminals are connected 0526 return true; 0527 } 0528 0529 QList<int> AbstractGrid::updateConnections() 0530 { 0531 // TODO: add int AbstractGrid::cellsCount() 0532 QList<bool> newConnections(m_width * m_height); 0533 for (uint i = 0; i < m_width * m_height; ++i) { 0534 newConnections[i] = false; 0535 } 0536 0537 // indexes of the changed cells 0538 QList<int> changedCells; 0539 changedCells.append(server_index); 0540 newConnections[server_index] = true; 0541 0542 while (!changedCells.isEmpty()) 0543 { 0544 int cell_index = changedCells.first(); 0545 int uindex = uCell(cell_index); 0546 int rindex = rCell(cell_index); 0547 int dindex = dCell(cell_index); 0548 int lindex = lCell(cell_index); 0549 0550 AbstractCell *cell = m_cells[cell_index]; 0551 AbstractCell *ucell = (uindex != NO_CELL) ? m_cells[uindex] : nullptr; 0552 AbstractCell *rcell = (rindex != NO_CELL) ? m_cells[rindex] : nullptr; 0553 AbstractCell *dcell = (dindex != NO_CELL) ? m_cells[dindex] : nullptr; 0554 AbstractCell *lcell = (lindex != NO_CELL) ? m_cells[lindex] : nullptr; 0555 0556 if ((cell->cables() & Up) && ucell != nullptr && 0557 (ucell->cables() & Down) && !newConnections[uindex]) { 0558 newConnections[uindex] = true; 0559 changedCells.append(ucell->index()); 0560 } 0561 if ((cell->cables() & Right) && rcell != nullptr && 0562 (rcell->cables() & Left) && !newConnections[rindex]) { 0563 newConnections[rindex] = true; 0564 changedCells.append(rcell->index()); 0565 } 0566 if ((cell->cables() & Down) && dcell != nullptr && 0567 (dcell->cables() & Up) && !newConnections[dindex]) { 0568 newConnections[dindex] = true; 0569 changedCells.append(dcell->index()); 0570 } 0571 if ((cell->cables() & Left) && lcell != nullptr && 0572 (lcell->cables() & Right) && !newConnections[lindex]) { 0573 newConnections[lindex] = true; 0574 changedCells.append(lcell->index()); 0575 } 0576 changedCells.erase(changedCells.begin()); 0577 } 0578 0579 // changedCells is empty here 0580 for (uint i = 0; i < m_width * m_height; i++){ 0581 AbstractCell *cell = m_cells[i]; 0582 if (cell->isConnected() != newConnections[i]) { 0583 changedCells.append(i); 0584 cell->setConnected(newConnections[i]); 0585 } 0586 } 0587 0588 return changedCells; 0589 } 0590