1

I have a very large 1D array containing Tile values for my game. Each value is a 4 digit number representing a type of tile (dirt, grass, air).

I save the values in a file using fstream like this:

std::fstream save("game_save", std::fstream::out | std::fstream::in);

So let's say I have a tiny map. 3 tiles wide and 3 tiles tall, all dirt (dirt value being 0001).

In the game it would look like

0001 0001 0001

0001 0001 0001

0001 0001 0001

In the file it looks like (just one dimensional)

0001 0001 0001 0001 0001 0001 0001 0001 0001

What would I do if I wanted to go to the 5th value (2nd row 2nd column) and change only that value to let's say 0002? So that way when I run the game again and it reads the file it sees:

0001 0001 0001 0001 0002 0001 0001 0001

Any advice on how to do this would be appreciated

Rapture686
  • 411
  • 1
  • 4
  • 9

7 Answers7

2

If you're absolutely sure of the 4 digits + exactly 1 space for each element and no tabl or newline occur in the file, you could use seekp(n*5,ios_base::beg) to position your next writing on the n-th element and just overwrite it.

Suggesions

If using this kind of positionning is safer with files opened with the ios::binary mode.

In that case, you could as well consider reading/writing the binary data using the block functions read()/write() and using the n*sizeof(tile) to find the right position. The file is then no longer fully platform independent, and it's not possible to edit it manually with a text editor, but you'd have improved performance, especially if you have very big terrains, and even more if you often access consecutive elements in the same line.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • 2
    Double plus on the binary mode recommendation. If you define your tile structure with fixed width datatypes the only cross platform problems you should have will be endian flips. – user4581301 Jul 17 '15 at 23:17
1

The EASY way is to just write the entire array again. Particularly as it's fairly short. If you KNOW that each element is exactly 5 butes, you can set the write-location with seekp, so save.seekp((1 * 3 + 1) * 5) and then write that value alone. But it's probably more work than it's worth if your file isn't HUGE (the actual file will still be updated in at least 1 sector, which is 512 or 4096 bytes on the hard-disk)

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
0
int loc=5;
save.seekg ((loc-1)*5, save.beg);
save << "0002";

try that guy :)

The selected answer on C++ Fstream to replace specific line? seems like a pretty good explanation.

Community
  • 1
  • 1
dufresnb
  • 135
  • 10
0

What you want to do is seek to the correct portion of our output stream.

fstream save;
...
save.seekp(POSITION_IN_FILE);

here's a full example:

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

#define BYTES_PER_BLOCK 5

void save_to_file(fstream& save, int value, int x, int y);
string num2string(int val);

int main(){
 fstream save("game_save", std::fstream::out | std::fstream::in);

 save_to_file(save, 2, 1, 1);
 save.close();
 return 0;
}

void save_to_file(fstream& save, int value, int x, int y){
  int pos = (y * 3 + x) * BYTES_PER_BLOCK;
  save.seekp(pos);
  save << num2string(value);
}

string num2string(int val){
 string ret = "";
 while (val > 0){
   ret.push_back('0'+val%10);
   val /= 10;
 }
 while (ret.length() < 4){
  ret.push_back('0');
 }
 reverse(ret.begin(), ret.end());
 return ret;
}
Timothy Murphy
  • 1,322
  • 8
  • 16
0

I suggest using memory mapped files instead of fstream. You can use boost for that. Boost library documentation

There is stack overflow thread that covers information about file mapping. Stack overflow Thred

Community
  • 1
  • 1
gandgandi
  • 330
  • 1
  • 8
0

It will be something along the lines of

save.seekp((row * number_columns + col)* size_of_element_data);
save.write(element_data, size_of_element_data);

But it will be a lot easier and safer to just read the file back in, edit the element, and write the whole file back out. You can't resize or insert an element in the middle of a file without shuffling the rest of the file backwards (which adds the problem of clearing the now-unused space at the end of the file) or forwards (which requires you to pad the end of the file stream and then perform a non-destructive move).

user4581301
  • 33,082
  • 7
  • 33
  • 54
0

I'd use a memory mapped file here and preferrably use binary representation too.

That way you can just store an array of ints and not need any (de)serialization code and/or seeking.

E.g.

Live On Coliru

#include <stdexcept>
#include <iostream>
#include <algorithm>

// memory mapping
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include <cstring>
#include <cassert>

template <size_t N, size_t M, typename T = int> 
struct Tiles {

    Tiles(char const* fname)
        : fd(open(fname, O_RDWR | O_CREAT)) 
    {
        if (fd == -1) {
            throw std::runtime_error(strerror(errno));
        }

        auto size = N*M*sizeof(T);
        if (int err = posix_fallocate(fd, 0, size)) {
            throw std::runtime_error(strerror(err));
        }

        tiles = static_cast<T*>(mmap(nullptr, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0));

        if (MAP_FAILED == tiles) {
            throw std::runtime_error(strerror(errno));
        }
    }

    ~Tiles() {
        if (-1 == munmap(tiles, N*M*sizeof(T))) {
            throw std::runtime_error(strerror(errno));
        }
        if (-1 == close(fd)) {
            throw std::runtime_error(strerror(errno));
        }
    }

    void init(T value) {
        std::fill_n(tiles, N*M, value);
    }

    T& operator()(size_t row, size_t col) { 
        assert(row >= 0 && row <= N);
        assert(col >= 0 && col <= M);
        return tiles[(row*M)+col];
    }

    T const& operator()(size_t row, size_t col) const { 
        assert(row >= 0 && row <= N);
        assert(col >= 0 && col <= M);
        return tiles[(row*M)+col];
    }
  private:
    int fd   = -1;
    T* tiles = nullptr;
};

int main(int argc, char** argv) {
    using Map = Tiles<3, 3, uint16_t>;

    Map data("tiles.dat");

    if (argc>1) switch(atoi(argv[1])) {
        case 1: 
            data.init(0x0001);
            break;
        case 2:
            data(1, 1) = 0x0002;
            break;
    }
}

Which prints:

clang++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && set -x; for cmd in 0 1 2; do ./a.out $cmd; xxd tiles.dat; done
+ for cmd in 0 1 2
+ ./a.out 0
+ xxd tiles.dat
0000000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000010: 0000                                     ..
+ for cmd in 0 1 2
+ ./a.out 1
+ xxd tiles.dat
0000000: 0100 0100 0100 0100 0100 0100 0100 0100  ................
0000010: 0100                                     ..
+ for cmd in 0 1 2
+ ./a.out 2
+ xxd tiles.dat
0000000: 0100 0100 0100 0100 0200 0100 0100 0100  ................
0000010: 0100                                     ..
sehe
  • 374,641
  • 47
  • 450
  • 633