sobota, 23 marca 2013

Edytor map w MS Excel / LibreOffice Calc

Pare dni temu widziałem prostą gierkę pisaną przez początkującego programistę. Gra polega na chodzeniu ludzikiem (znak ASCII) po mapie rysowanej kodem ASCII. Nie ma szału, ale to doskonały projekt aby wykazać że dobra organizacja kodu ratuje (grzebie) projekt.

Osoba pisząca tą grę wymyśliła sobie problem rysowania mapy w ten sposób, że każda mapa była rysowana oddzielną procedurą. Okey... ale skąd brano topologie mapy? No jasne, że z palca! Każda linia mapy była pisana z palca, znak w znak. W zasadzie procedury rysujące mapy różniły się jedynie sekwencją znaków, a nie samym działaniem. Koszmar :/

Podrzuciłem pomysł rysowania mapy w Excelu. Tak, tak.. w tym arkuszu kalkulacyjnym. Tutaj pokaże to samo używają OpenOffice / LibreOffice Calc, aczkolwiek w Excel też zadziała.

Moja gra będzie oparta na scenariuszu chodzenia po mapie przypominającej labirytnt. Zakładam, że ściany będę oznaczał symbolem "x", podczas gdy punkt startowy gracza oznaczę "@", a punkt docelowy przez "&". Okey, a co ma do tego Excel?

Zaznaczam wszystkie wiersze i kolumny ( przycisk nad wierszem 1, na lewo od kolumny A ), ustalam szerokość kolumny na 0,8 ( zlokalizowane arkusze kalkulacyjne używają przecinka jako separatora części ułamkowej ).


Teraz cały arkusz wygląda mniej-więcej, jakby był złożony z kwadratów. Chciałbym, aby po wstawieniu w komórkę znaku x ta komórka się zaczerniła. Dzięki temu będę lepiej widział jak wygląda rysowana mapa. Aby nie kolorować ręcznie, użyję formatowania warunkowego.
Ustalam 3 reguły:
  • czarne tło dla znaku x
  • zielone tło dla znaku startu @
  • czerwone dla końca &


[ LOffice Calc sprytnie zauważy, że czarny tekst na czarnym tle jest średnio widoczny, więc automatycznie wypełnione na czarno komórki będą miały kolor czcionki zmieniony na biel ]

Mapa wyświetlana na terminalu Windows ma domyślnie 80x25 znaków, czyli plansza w Excel zaczyna się na komórce (A,1) a kończy na (CB, 25). Postawiłem ramkę wokół tego obszaru + zmieniłem domyślne tło na niebieski. Dzięki temu nie przedobrzę z obszarem rysowania.

Tak oto za pomocą stawiania x-ów, jednego startu i jednego końca namalowałem mapę. Tutaj jest ona dużo węższa, niż szerokość terminala, ale to w niczym nie przeszkadza.


Ważne, że to co widać w Excel będzie wiernie odwzorowane na ekranie gracza. Tak zrobioną mapę zapisuję do formatu CSV ( comma separate values ). Jako separator ustawiam średnik ( ; ). Urok formatu CSV polega na tym, że w wypadku danych numerycznych obsługuje go każdy cywilizowany program obliczeniowy - począwszy od R przez Matlaby, MathCADy, Mathematica, Octave, NumPy a kończywszy na Excel i Calc. Dodatkowo ten format jest idealny do parsowania. Prościej się nie da.

Po obejrzeniu tego pliku w dowolnym edytorze tekstowym powinien on wyglądać mniej więcej tak:
Puste komórki zostały pominięte, czyli zapis ;; oznacza, że dana komórka nie miała wartości. Obraz może wydawać się "rozjechany", ale bezproblemowo można go poskładać, tj. usunąć znak separatora, a puste pola zamienić na spację. Terminale używają czcionek monospace, dzięki czemu całość pięknie wyrówna się na ekranie.

Na szybko napisałem funkcję, która realizuje wyświetlenie takiego pliku mapy w formacie CSV na konsoli. Kod:

#include <iostream>
#include <fstream>
using namespace std;

inline void parseLine(string s)
{
 char c;
 char delim=';', fill=' ';
 size_t pos=-1;
 
 if (s.empty()) return;
 
 do {
  pos = s.find(delim, pos+1);
  if ( pos != string::npos )
  {
   if ( pos==0 || s[pos-1]==delim) cout << fill;
   else cout << s[pos-1];
  }
 } while (pos != string::npos);
 
 cout << ( c=s[s.size()-1],c == delim ? fill : c ) << endl;
}

int main()
{
 ifstream mapa("mapa.csv");
 string s;
 

 if (!mapa)
 {
  perror("Blad ladowania mapy");
  return -1;
 }

 while ( !mapa.eof() && mapa )
 {  
  getline(mapa, s);
  parseLine(s);
 }

 mapa.close();
 return 0;
}

W rezultacie całość wygląda w konsoli tak:


Przy tak prostych aplikacjach wysilanie się na własny edytor map jest przerostem formy nad treścią. Bardzo zachęcam do korzystania np. z Calc/Excela w takich sytuacjach.

2 komentarze:

  1. "...do formatu CSV ( comma separate values ). Jako separator ustawiam średnik ( ; )."

    No to już chyba nie jest format "comma separated values". Widziałem że MS to nazywał "character separated values".

    Bardzo fajny post, dobry pomysł, tak trzymać,
    Tomek

    OdpowiedzUsuń
    Odpowiedzi
    1. Masz rację, natomiast pisząc CSV mam na myśli pewien format, bo sam separator pól jest umowny (zob. http://en.wikipedia.org/wiki/.csv).

      --- Wikipedia ---
      Rather, in practice the term "CSV" refers to any file that:

      -is plain text using a character set such as ASCII, Unicode, EBCDIC, or Shift JIS
      -consists of records (typically one record per line)
      -with the records divided into fields separated by delimiters (typically a single reserved character such as comma, semicolon, or tab; sometimes the delimiter may include optional spaces)
      -where every record has the same sequence of fields.
      --- Wikipedia ---

      Dodatkowo, zlokalizowany Excel/LOffice Calc używa przecinka jako separatora części ułamkowej, więc bezpiecznie jest wyeksportować z separatorem w postaci średnika. Excel 2010 i LOffice Calc 4 używają średnika jako domyślnego separatora (z przyczyn zapisu liczb ułamkowych w Polsce z użyciem przecinka).
      Niemniej jednak w linii 8 wystarczy zmienić znak "delim" na co tylko chcesz i już będzie działało :))

      Usuń