Skip to content

File WorldGrid.hpp

File List > core > WorldGrid.hpp

Go to the documentation of this file



#pragma once

#include <cassert>
#include <fstream>
#include <iostream>
#include <unordered_map>
#include <vector>

#include "CoreObject.hpp"
#include "Data.hpp"
#include "GridPosition.hpp"

namespace cse491 {

class WorldGrid : public CoreObject {
protected:
  size_t width = 0;           
  size_t height = 0;          
  std::vector<size_t> cells;  

  // -- Helper functions --

  [[nodiscard]] inline size_t ToIndex(size_t x, size_t y) const { return x + y * width; }

  // -- Serialize and Deserialize functions --
  // Mechanisms to efficiently save and load the exact state of the grid.
  // File format is width and height followed by all
  // values in the grid on each line thereafter.

  std::string GetTypeName_impl() const override { return "cse491::WorldGrid"; }

  void Serialize_impl(std::ostream &os) const override {
    SerializeValue(os, width);
    SerializeValue(os, height);
    for (size_t state : cells) SerializeValue(os, state);
  }

  void Deserialize_impl(std::istream &is) override {
    DeserializeValue(is, width);
    DeserializeValue(is, height);
    cells.resize(width * height);
    for (size_t &state : cells) DeserializeValue(is, state);
  }

public:
  WorldGrid() = default;
  WorldGrid(size_t width, size_t height, size_t default_type = 0)
      : width(width), height(height), cells(width * height, default_type) {}
  WorldGrid(const WorldGrid &) = default;
  WorldGrid(WorldGrid &&) = default;

  WorldGrid &operator=(const WorldGrid &) = default;
  WorldGrid &operator=(WorldGrid &&) = default;

  // -- Accessors --
  [[nodiscard]] size_t GetWidth() const { return width; }
  [[nodiscard]] size_t GetHeight() const { return height; }
  [[nodiscard]] size_t GetNumCells() const { return cells.size(); }

  [[nodiscard]] bool IsValid(double x, double y) const {
    return x >= 0.0 && x < width && y >= 0.0 && y < height;
  }

  [[nodiscard]] bool IsValid(GridPosition pos) const { return IsValid(pos.GetX(), pos.GetY()); }

  [[nodiscard]] size_t At(size_t x, size_t y) const {
    assert(IsValid(x, y));
    return cells[ToIndex(x, y)];
  }

  [[nodiscard]] size_t &At(size_t x, size_t y) {
    assert(IsValid(x, y));
    return cells[ToIndex(x, y)];
  }

  [[nodiscard]] size_t At(GridPosition p) const { return At(p.CellX(), p.CellY()); }

  [[nodiscard]] size_t &At(GridPosition p) { return At(p.CellX(), p.CellY()); }

  [[nodiscard]] size_t operator[](GridPosition p) const { return At(p); }
  [[nodiscard]] size_t &operator[](GridPosition p) { return At(p); }

  // Size adjustments.
  void Resize(size_t new_width, size_t new_height, size_t default_type = 0) {
    // Create a new vector of the correct size.
    std::vector<size_t> new_cells(new_width * new_height, default_type);

    // Copy the overlapping portions of the two grids.
    size_t min_width = std::min(width, new_width);
    size_t min_height = std::min(height, new_height);
    for (size_t x = 0; x < min_width; ++x) {
      for (size_t y = 0; y < min_height; ++y) {
        new_cells[x + y * new_width] = cells[ToIndex(x, y)];
      }
    }

    // Swap the new grid in; let the old grid be deallocated in its place.
    std::swap(cells, new_cells);
    width = new_width;
    height = new_height;
  }

  // -- Read and Write functions --
  // These are the same idea as Save and Load, but they are human readable, but they
  // also require that each state has been assigned a unique character symbol.

  void Write(std::ostream &os, const type_options_t &types) const {
    size_t cell_id = 0;
    for (size_t y = 0; y < height; ++y) {
      for (size_t x = 0; x < width; ++x) {
        os << types[cells[cell_id++]].symbol;
      }
      os << '\n';
    }
    os.flush();
  }

  bool Write(std::string filename, const type_options_t &types) const {
    std::ofstream os(filename);
    if (!os.is_open()) {
      std::cerr << "Could not open file '" << filename << "' to write grid." << std::endl;
      return false;
    }
    Write(os, types);
    return true;
  }

  void Read(std::istream &is, const type_options_t &types) {
    // Build a symbol chart for conversions back.
    std::unordered_map<char, size_t> symbol_map;
    for (size_t i = 0; i < types.size(); ++i) {
      symbol_map[types[i].symbol] = i;
    }

    // Load the file into memory.
    std::vector<std::string> char_grid;
    std::string line;
    width = 0;
    while (std::getline(is, line)) {
      char_grid.push_back(line);
      if (line.size() > width) width = line.size();
    }
    height = char_grid.size();

    // Convert each symbol to the appropriate value.
    cells.resize(width * height);
    size_t cell_id = 0;
    for (size_t y = 0; y < height; ++y) {
      for (size_t x = 0; x < width; ++x) {
        // Use the cell values provided, or zero if a cell position is missing.
        cells[cell_id++] = (x < char_grid[y].size()) ? symbol_map[char_grid[y][x]] : 0;
      }
    }
  }

  bool Read(std::string filename, const type_options_t &types) {
    std::ifstream is(filename);
    if (!is.is_open()) {
      std::cerr << "Could not open file '" << filename << "' to write grid." << std::endl;
      return false;
    }
    Read(is, types);
    return true;
  }
};

}  // End of namespace cse491