Diskuze: Nacteni mapy z txt souboru + parsovani
V předchozím kvízu, Online test znalostí C++, jsme si ověřili nabyté zkušenosti z kurzu.


DarkCoder:20.11.2020 22:03
Spíše než načítání dat pro mapu to vypadá jako bys pomalu analyzoval zdrojový soubor. Což neznamená, že to tak nemůžeš dělat, ale je to zbytečně komplikované. Doporučuji podívat se, jak např. vypadá struktura .BMP souboru. Soubor rozděl na části hlavička a data. Načtení mapy je prosté, načtení znaku ve dvou for cyklech. Oddělovače můžeš úplně vynechat.
Luboš Horký:21.11.2020 10:50
Díval jsem se na nějaké podobné příklady, kde povětšinou používají XML formát, který obsahuje hlavičku a data. Z tohoto souboru potom čtou vše potřebné, ale chtěl bych si tvořit vlastní txt soubory ve kterých bych měl informace o každé mapě a ty si načítal.
Nějak mi nesedí, že mohu vynechat oddělovač, přeci když budu číst
znaky tak se přečte i "," a uloží se do pole. Já bych si představoval
pole[pocet_dlazdic_na_mapu], kde do každého indexu bych načetl jaká
"dlaždice" (číslo z txt souboru) se zde nachází.
Nebo potom načítat hodnoty z txt do 2rozměrného pole kde bych měl
[radek][sloupec].
Ale celkově moc nerozumím vytvořenému "CfgFile.cpp" ve kterém se používají struktury Tokeny, které mají ID, Value, Section. nevím jak bych tyhle tokeny použil pro můj případ.
DarkCoder:21.11.2020 12:16
Způsobů, jakým to lze udělat je bezpočet. Ten, ve kterém je uveden identifikátor s hodnotou se využívá tam, kde data mohou být v souboru v různém pořadí nebo mohou dokonce některá chybět, aniž by to ovlivnilo chod programu. Je to komplexnější, ale také o dost pomalejší. Soubor map má obvykle pevnou strukturu. Vždy se bude skládat z hlavičky a samotných dat. Takový soubor se obvykle netvoří manuálně ale je výstup nějakého editoru. Pro efektivitu je soubor vytvořen v binární podobě. Textová je méně efektivní, její výhodou je však snazší editace. Data v souboru vůbec nemusí být v souboru uložena jako 2D pole. Rozhození dat je prováděno programově. Jediné, co je třeba je načtení požadovaného počtu znaků ze souboru a toto množství je dáno dvěma hodnotami v hlavičce.
Binární podoba zajišťuje přesnou velikost jednotlivých dat a lze k nim tak přistupovat přímo. To je jedním z důvodů, proč lze oddělovače vynechat. Pro info, výstupní funkce printf() a fprintf() umožňují přečíst data a tato data zahodit (nepřiřadit je do proměnné). To není v binární podobě třeba.
Představ si nejjednodušší mapový soubor vytvoření v binární podobě, kde hlavička je tvořena dvěma unsigned int hodnotami (2x4 byty), zbytek souboru představují samotná data. První hodnota představuje počet dlaždic na výšku, druhá počet dlaždic na šířku. Díky binární reprezentaci mají tyto hodnoty stále stejnou velikost (každá 4 byty). Je tedy jedno zda hodnota je 1, 10, 100, 1000, 10000.. Pořád to budou 4 byty. Druhá hodnota bude vždy na 5 až 8 bytu. Data budou zabírat také stejnou velikost (každá 1 byte - rozsah unsigned char 0 až 255). Pak stačí vždy postupně číst tato data pomocí dvou for cyklů (počet iterací je dán součinem první a druhé hodnoty v hlavičce) a hodnotu přiřadit do té které části pole, v závislosti na hodnotě řídících proměnných.
Z toho je patrné, jak je pevná struktura mapového souboru užitečná a realizace načítání jednoduchá a efektivní.
Pro info, výstupní funkce printf() a fprintf() umožňují přečíst data a tato data zahodit (nepřiřadit je do proměnné).
Oprava: Pro info, vstupní funkce scanf() a fscanf() umožňují...
Luboš Horký:21.11.2020 15:11
Díky moc za plno informací které jsem si nebyl schopen spojit.
Jen si nejsem jistý jestli jsem správně pochopil čtení pomocí dvou cyklů a ukládání číselných hodnot podle velikostí bytů.
Proto abych si zkusil vytvořit program který načte "moji mapu" jsem si
vytvořil nový projekt, ve kterém jsem to testoval a přišel jsem na tohle
řešení. Nejsem si jistý jestli je to řešení takové, které jsi měl na
mysli
Teď bych si chtěl vytvořit v "CfgFile.cpp" podobnou funkci, díky které
si načtu hodnoty z dané sekce do pole. Ale pokud mi to nepůjde, tak se
prozatím rozhodnu tak, že budu načítat mapu zvlášť z jiného txt souboru
pomocí této funkce, abych se pohnul a mohl na projektu pokračovat.
Poté zkusím konzultaci jestli mi někdo poradí jak tuto funkci zakomponovat
do CfgFile
Ale kdo ví třeba se mi to povede
Zde je kód na přečtení čísel ze souboru a uložení do pole[y][x], je možné že jsou tam chyby, ale prozatím funguje jak má.
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
#pragma warning(disable : 4996)
int main() {
unsigned int vyska = 24;
unsigned int sirka = 32;
FILE* soubor;
soubor = fopen("mapa1.cfg", "rt");
int CislaMapa[24][32];
int i;
if (soubor == NULL) {
printf("Nelze otevrit soubor\n");
exit(0);
}
//cteni souboru do pole cisel
for (int x = 0; x < vyska; x++) {
for (i = 0; i < sirka; i++)
{
fscanf(soubor, "%d,", &CislaMapa[x][i]);
}
}
fclose(soubor);
//zobrazeni nacteneho pole v konzoli
for (int x = 0; x < vyska; x++) {
for (i = 0; i < sirka; i++) {
cout << CislaMapa[x][i] << " ";
}
cout << endl;
}
cout << endl << CislaMapa[0][2] << endl; //zkouska radek 1 sloupec 3
cout << endl << CislaMapa[23][31] << endl; //zkouska vypsani posledniho prvku v souboru
return 0;
}
texťák:
5,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8
0,50,0,01,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,54
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,250
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,05
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-8
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-22
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
-1,2,3,4,55,66,7,8,9,10,1,55,54,50,05,04,04,20,540,50,40,10,570,70,05,04,50,03,30,30,30,330
DarkCoder:21.11.2020 16:44
Že je třeba dvou for cyklů pro uložení dat uložených do pole jsi pochopil správně. Jen Ti ještě uniká to, že všechna data budou mít stejnou velikost a to jeden byte. Je to proto, že pracuješ v textovém režimu nikoli v binárním. V textovém režimu bude mít hodnota 1 velikost 1 byte, 10 2 byty, 100 3 byty, kdežto v binárním režimu bude mít hodnota 1, 10 i 100 velikost 1 byte!
Aby si dosáhl toho co požaduješ, postupuj takto:
V prvé řadě si musíš nasimulovat mapový soubor. Vytvoř si program, který vytvoří soubor test.map a tento soubor otevři pro zápis v binárním režimu. Do souboru zapiš dvě hodnoty unsigned int představující rozměr mapy. Tyto dvě hodnoty budou simulovat hlavičku. Dále do souboru ihned za hlavičku zapiš náhodná data v rozsahu 0-255. Tyto data budou simulovat samotná data. Počet těchto dat bude logicky rovno součinu obou hodnot v hlavičce. Tímto si vygeneruješ testovací soubor, který použiješ pro tvůj program.
Dále vytvoř program, který otevře soubor test.map v binárním režimu. Přečti z něho dvě unsigned int hodnoty a ulož si je. Tyto hodnoty Ti poslouží pro získání rozměru 2D pole. Dále vytvoř 2D pole typu unsigned char pomocí dynamické alokace o rozměrech uložených hodnot. Nyní načti ze souboru pomocí dvou for cyklů zbylá data ze souboru do vytvořeného 2D pole.
Nyní máš data ze souboru test.map uloženy v paměti pro další použití.
Soubor test.map obsahuje pouze to co potřebuješ, nic víc navíc. Načtení dat je přímočaré bez jakýchkoli nutných úprav.
Ještě ke kódu. Příliš mícháš C s C++ dohromady (iostream vs stdio.h, printf() vs cout).. Zjednoduš to..
DarkCoder:21.11.2020 21:59
Zde se můžeš inspirovat jak vytvářet a načítat data z mapového
souboru.
První program vytváří jednoduchý mapový soubor, druhý z něj čte a
zobrazuje jeho data na obrazovku.
Vytvoření mapového souboru
// Map File Generator
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MIN 5
#define MAX 10
int main(void) {
FILE* fp = NULL;
int row, col;
char *fname = "test.map";
srand((unsigned)time(NULL));
// gerenovani hlavicky
row = (rand() % (MAX - MIN + 1)) + MIN;
col = (rand() % (MAX - MIN + 1)) + MIN;
// printf("row = %d, col = %d\n", row, col);
fp = fopen(fname, "wb");
if (!fp) exit(1);
// zapis hlavicky do soubory
fwrite(&row, sizeof(int), 1, fp); // MIN - MAX
fwrite(&col, sizeof(int), 1, fp); // MIN - MAX
// generovani a zapis dat do souboru
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
fputc(rand() % (UCHAR_MAX + 1), fp); // 0 - 255
}
}
fclose(fp);
return(0);
}
Čtení mapového souboru
// Map File Reader
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE* fp = NULL;
int row, col;
char* fname = "test.map";
fp = fopen(fname, "rb");
if (!fp) exit(1);
// nacteni hlavicky ze souboru
fread(&row, sizeof(int), 1, fp);
fread(&col, sizeof(int), 1, fp);
// printf("row = %d, col = %d\n", row, col);
// Zde by se dynamicky alokovalo 2D pole typu unsigned char
// nacteni dat ze souboru a vypis na obrazovku
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
printf("%3d ", fgetc(fp));
// Zde by se nacitaly data ze souboru a prirazovaly do 2D pole
}
putchar('\n'); // formatovani dat do 2D pro vypis na obrazovku
}
fclose(fp);
return(0);
}
Ukládání dat do pole není implementováno, avšak komentáře vystihují patřičné kroky.
+20 Zkušeností
+2,50 Kč

Luboš Horký:22.11.2020 19:40
Paráda, díky moc. Je pravda že práci s binárními soubory nemám
odzkoušenou, proto raději používám textové, i přes to že si uvědomuji
jaké to má nevýhody a jak si komplikuji práci.
Tento postup s binárními si odzkouším v testovací "appce" a do projektu
zatím dám textový se kterým si jsem více jistý
Ale rozhodně děkuji za rady, myslím že se v budoucnu budou zase hodit
Lepsi by bylo pouzit text/XML nebo text/CSV.
Mapu bych ukladal jako string.
Takhle mam treba resenou mapu pro hru sokoban. Ulozene rozmery x,y, mapa
pozadi, pozice panacka, beden.
https://mlich.zam.slu.cz/…y/sokolvl.js
https://mlich.zam.slu.cz/…/sokoban.htm
Zobrazeno 10 zpráv z 10.