char_t Square::ToGrid(int ch) { if (ch == Black[0] || ch == puzT('[') || ch == puzT('*')) return 0; #if PUZ_UNICODE return iswprint(ch) ? towupper(ch) : 0; #else return isprint(ch) ? toupper(ch) : 0; #endif // PUZ_UNICODE }
// Default constructor // Square is white and blank. Square::Square() : m_col(-1), m_row(-1), m_flag(FLAG_CLEAR), m_number(), m_red(255), m_green(255), m_blue(255) { SetText(puzT("")); SetSolution(puzT("")); // These will be filled in by the grid m_next.clear(); }
// From CamelCase to snake_case std::string CamelCase(const string_t & name) { // Replace '_' + a lowercase letter with an upper case letter string_t str; str.reserve(name.size()); for (size_t i = 0; i < name.size(); ++i) { if (i == 0 || name[i] == puzT('_')) { if (i > 0) ++i; if (i < name.size()) { #if PUZ_UNICODE if (::iswlower(name[i])) str.push_back(::towupper(name[i])); #else // ! PUZ_UNICODE if (::islower(name[i])) str.push_back(::toupper(name[i])); #endif // PUZ_UNICODE/! PUZ_UNICODE else // Not lower case: so push both the underscore and letter { if (i > 0) str.push_back(name[i-1]); str.push_back(name[i]); } } else if (i > 0) // At the end of the string: push the underscore str.push_back(name[i-1]); } else // Not a hypen: push the letter str.push_back(name[i]); } return encode_utf8(str); }
string_t Square::GetHtmlColor() const { char_t ret[] = { puzT('#'), hexDigits[(m_red & 0xf0) >> 4], hexDigits[m_red & 0x0f], hexDigits[(m_green & 0xf0) >> 4], hexDigits[m_green & 0x0f], hexDigits[(m_blue & 0xf0) >> 4], hexDigits[m_blue & 0x0f], };
static puz::string_t luapuz_checkstring_t(lua_State * L, int index) { try { return puz::decode_utf8(luaL_checkstring(L, index)); } catch (...) { luapuz_handleExceptions(L); } lua_error(L); return puzT(""); // This is just to stop the warnings. }
void Square::SetColor(const string_t & hexcolor) { size_t len = hexcolor.size(); if (len > 0 && hexcolor[0] == puzT('#')) { SetColor(hexcolor.substr(1)); return; } try { if (len == 3) SetColor(ParseHex(hexcolor[0], hexcolor[0]), ParseHex(hexcolor[1], hexcolor[1]), ParseHex(hexcolor[2], hexcolor[2])); else if (len == 6) SetColor(ParseHex(hexcolor[0], hexcolor[1]), ParseHex(hexcolor[2], hexcolor[3]), ParseHex(hexcolor[4], hexcolor[5])); } catch (Exception &) { // Don't set the color if we can't parse the hex value } }
//----------------------------------------------------------------------------- // SaveXPF //----------------------------------------------------------------------------- void SaveXPF(Puzzle * puz, const std::string & filename, void * /* dummy */) { const Grid & grid = puz->GetGrid(); if (grid.IsScrambled()) throw ConversionError("XPF does not support scrambled puzzles"); if (! grid.HasSolution()) throw ConversionError("XPF does not support puzzles without a solution"); for (const Square * square = puz->GetGrid().First(); square != NULL; square = square->Next()) { if (square->HasImage()) throw ConversionError("XPF does not support puzzles with background images."); } xml::document doc; xml::node puzzles = doc.append_child("Puzzles"); puzzles.append_attribute("Version") = "1.0"; xml::node puzzle = puzzles.append_child("Puzzle"); // Metadata puz::Puzzle::metamap_t & meta = puz->GetMetadata(); puz::Puzzle::metamap_t::iterator it; for (it = meta.begin(); it != meta.end(); ++it) { if (it->first == puzT("notes")) // "notes" -> "Notepad" xml::Append(puzzle, "Notepad", it->second); else xml::Append(puzzle, xml::CamelCase(it->first).c_str(), it->second); } // Grid if (grid.GetType() == TYPE_DIAGRAMLESS) xml::Append(puzzle, "Type", "diagramless"); // Grid Size xml::node size = puzzle.append_child("Size"); xml::Append(size, "Rows", puz::ToString(grid.GetHeight())); xml::Append(size, "Cols", puz::ToString(grid.GetWidth())); // Answers { xml::node grid_node = puzzle.append_child("Grid"); xml::node row; std::string row_text; row_text.reserve(grid.GetWidth()); const Square * square; for (square = grid.First(); square; square = square->Next()) { if (square->IsFirst(ACROSS)) row = grid_node.append_child("Row"); if (square->IsMissing()) row_text.append(1, '~'); else if (square->IsBlack()) row_text.append(1, char(Square::Black[0])); else if (square->IsSolutionBlank()) row_text.append(1, ' '); else row_text.append(1, square->GetPlainSolution()); if (square->IsLast(ACROSS)) { xml::SetText(row, row_text.c_str()); row_text.clear(); } } } // Circles { xml::node circles = puzzle.append_child("Circles"); const Square * square; for (square = grid.First(); square; square = square->Next()) { if (square->HasCircle()) { xml::node circle = circles.append_child("Circle"); circle.append_attribute("Row") = square->GetRow() + 1; circle.append_attribute("Col") = square->GetCol() + 1; } } if (circles.empty()) puzzle.remove_child(circles); } // Rebus { xml::node rebus = puzzle.append_child("RebusEntries"); const Square * square; for (square = grid.First(); square; square = square->Next()) { if (square->HasSolutionRebus()) { xml::node entry = rebus.append_child("Rebus"); entry.append_attribute("Row") = square->GetRow() + 1; entry.append_attribute("Col") = square->GetCol() + 1; entry.append_attribute("Short") = std::string(1, square->GetPlainSolution()).c_str(); xml::SetText(entry, square->GetSolution()); } } if (rebus.empty()) puzzle.remove_child(rebus); } // Shades { xml::node shades = puzzle.append_child("Shades"); const Square * square; for (square = grid.First(); square; square = square->Next()) { if (square->HasColor()) { xml::node shade = shades.append_child("Shade"); shade.append_attribute("Row") = square->GetRow() + 1; shade.append_attribute("Col") = square->GetCol() + 1; if (square->HasHighlight()) xml::SetText(shade, "gray"); else xml::SetText(shade, square->GetHtmlColor()); } } if (shades.empty()) puzzle.remove_child(shades); } // Clues { xml::node clues_node = puzzle.append_child("Clues"); Clues & clues = puz->GetClues(); Clues::iterator clues_it; for (clues_it = clues.begin(); clues_it != clues.end(); ++clues_it) { ClueList & cluelist = clues_it->second; ClueList::iterator it; for (it = cluelist.begin(); it != cluelist.end(); ++it) { xml::node clue = clues_node.append_child("Clue"); // Find the clue direction if (! puz->IsDiagramless()) { const Word & word = it->GetWord(); switch (word.GetDirection()) { case ACROSS: clue.append_attribute("Dir") = "Across"; break; case DOWN: clue.append_attribute("Dir") = "Down"; break; case DIAGONAL_SW: clue.append_attribute("Dir") = "Diagonal"; break; default: throw ConversionError("XPF clues must be Across, Down, or Diagonal"); break; } clue.append_attribute("Row") = word.front()->GetRow() + 1; clue.append_attribute("Col") = word.front()->GetCol() + 1; } else // diagramless { puz::string_t title = cluelist.GetTitle(); if (title == puzT("Across")) clue.append_attribute("Dir") = "Across"; else if (title == puzT("Down")) clue.append_attribute("Dir") = "Down"; else if (title == puzT("Diagonal")) clue.append_attribute("Dir") = "Diagonal"; else throw ConversionError("XPF clues must be Across, Down, or Diagonal"); } clue.append_attribute("Num") = encode_utf8(it->GetNumber()).c_str(); // Clue formatting needs to be escaped if it is XHTML. // xml::SetInnerXML(clue, it->GetText()); xml::SetText(clue, it->GetText()); } } } // User Grid { xml::node usergrid = puzzle.append_child("UserGrid"); xml::node row; std::string row_text; row_text.reserve(grid.GetWidth()); const Square * square; for (square = grid.First(); square; square = square->Next()) { if (square->IsFirst(ACROSS)) row = usergrid.append_child("Row"); if (square->IsMissing()) row_text.append(1, '~'); else if (square->IsBlack()) row_text.append(1, char(Square::Black[0])); else if (square->IsBlank()) row_text.append(1, ' '); else row_text.append(1, square->GetPlainText()); if (square->IsLast(ACROSS)) { xml::SetText(row, row_text.c_str()); row_text.clear(); } } if (usergrid.empty()) puzzle.remove_child(usergrid); } // User Rebus { xml::node rebus = puzzle.append_child("UserRebusEntries"); const Square * square; for (square = grid.First(); square; square = square->Next()) { if (square->HasTextRebus()) { xml::node entry = rebus.append_child("Rebus"); entry.append_attribute("Row") = square->GetRow() + 1; entry.append_attribute("Col") = square->GetCol() + 1; entry.append_attribute("Short") = std::string(1, square->GetPlainText()).c_str(); xml::SetText(entry, square->GetText()); } } if (rebus.empty()) puzzle.remove_child(rebus); } // Square Flags { xml::node flags = puzzle.append_child("SquareFlags"); const Square * square; for (square = grid.First(); square; square = square->Next()) { if (square->GetFlag() != 0) { xml::node entry = flags.append_child("Flag"); entry.append_attribute("Row") = square->GetRow() + 1; entry.append_attribute("Col") = square->GetCol() + 1; if (square->HasFlag(FLAG_PENCIL)) entry.append_attribute("Pencil") = "true"; if (square->HasFlag(FLAG_BLACK)) entry.append_attribute("Checked") = "true"; if (square->HasFlag(FLAG_REVEALED)) entry.append_attribute("Revealed") = "true"; if (square->HasFlag(FLAG_X)) entry.append_attribute("Incorrect") = "true"; if (square->HasFlag(FLAG_CORRECT)) entry.append_attribute("Correct") = "true"; } } if (flags.empty()) puzzle.remove_child(flags); } // Timer { if (puz->GetTime() != 0) { xml::node timer = puzzle.append_child("Timer"); timer.append_attribute("Seconds") = puz->GetTime(); if (puz->IsTimerRunning()) timer.append_attribute("Running") = "true"; } } doc.save_file(filename.c_str()); }
try { if (len == 3) SetColor(ParseHex(hexcolor[0], hexcolor[0]), ParseHex(hexcolor[1], hexcolor[1]), ParseHex(hexcolor[2], hexcolor[2])); else if (len == 6) SetColor(ParseHex(hexcolor[0], hexcolor[1]), ParseHex(hexcolor[2], hexcolor[3]), ParseHex(hexcolor[4], hexcolor[5])); } catch (Exception &) { // Don't set the color if we can't parse the hex value } } static const char_t hexDigits [] = puzT("0123456789abcdef"); string_t Square::GetHtmlColor() const { char_t ret[] = { puzT('#'), hexDigits[(m_red & 0xf0) >> 4], hexDigits[m_red & 0x0f], hexDigits[(m_green & 0xf0) >> 4], hexDigits[m_green & 0x0f], hexDigits[(m_blue & 0xf0) >> 4], hexDigits[m_blue & 0x0f], }; return string_t(ret, 7); }
explicit Clue(int num_, const string_t & text_ = puzT("")) { SetNumber(num_); SetText(text_); }