poniedziałek, 16 lipca 2012

Analogowe literały w C++

Ostatnio w pracy troszkę się nudziło (bywa ;)), co spowodowało, że następujący kod zaczął się kompilować:
for (uint worm = I-------------I; worm >= II; worm -= I-I ) {
   cout << '*';
}

uint monsterHP =  I-----------------------I;       // 12 HP
uint monBossHP = (I-----------------------I) * 10; // 120 HP

cout <<( I-------I
         |       !
         !       !
         I-------I ).area(); 
To nie jest mój pomysł - kiedyś natrafiłem na blogu Paczesiowej ciekawy wpis gdzie znalazłem link opisujący w C++ literały analogowe - czyli zamiast podawać wartości naturalne można było narysować sobie linie, prostokąt albo sześcian w ASCII, co następnie było zamieniane na wartość liczbową.

Niestety - strona przestała działać a w cache znalazłem jedynie podgląd jak to wyglądało... więc nie miałem innego wyjścia jak spróbować zaimplementować to samemu ;)

Na razie odpuściłem sobie implementację literałów 3D - zaimplementowałem tylko linie oraz prostokąty co okazało się zaskakująco proste, przy czym nie nakładałem zbyt wysokich wymagań co do poprawności i obsługi sytuacji brzegowych :)

Cała sztuczka polega na odpowiednim przeładowaniu operatorów. Zauważ, że tak naprawdę linię można ograniczyć dwoma obiektami (I), których wartość ulega zmianie poprzez użycie operatora "--" oraz połączenie ich przez binarny operator "-".

Analogicznie jest dla 2D - to po prostu 2 linie z tym, że jedna jest modyfikowana przez "!" (zwiększa wysokość) a potem linia + linia z wysokością są scalane przez operator binarny "|".

Myślę, że rozjaśni się wszystko jak pokażę kod (testowany na VC2k5 i ideone). Kod jest brzydki i prototypowy - używać na własną odpowiedzialność a najlepiej zamknąć oczy czytając ;)
typedef unsigned int uint;

const uint II = 0;

/// Literal analogowy 1D oraz 2D
class _ALiteral
{
public:
 _ALiteral() : width(1), height(0) {}

 uint len() const { return width; }
 uint len2() const { return height; }
 float ratio() const { static_cast<float>(width) / height; }
 uint area() const { return width * height; }

 // niejawna konwersja do uint - czasem moze troche popsuc :(
 // ale umozliwia pisanie uint t = I---I; zamiast (I---I).len()
 operator uint() { return len(); }

 _ALiteral operator-(_ALiteral right) const {
   _ALiteral t;
   t.width = width + (right.width-1);
   t.height = right.height + height;
   return t;
 }

 _ALiteral& operator--() {
  width += 1;
  return *this;
 }

 _ALiteral operator | (_ALiteral right) {
   height = 1 + right.height; // bo | zastepuje jeden znak !
   height >>= 1; // bo ! jest aplikowane 2 razy, mozna dodac sprawdzanie parzystosci
   return *this;
 }

 _ALiteral& operator!() {
    height += 1;
    return *this;
 }

private:
 uint width, height;
};
// ukrycie konstrukcji obiektu _ALiteral
#define I _ALiteral()

Brak komentarzy:

Prześlij komentarz