   [C++] Creating game replays
  Hello, first off I'd like to state: I am not asking for help, but simply advice and suggestions.This is (of course) related to Snake. A few days ago I wrote a little sub-routine for creating (only that, not viewing) replays. It works, all fine-and-handy-dandy. This is what I went with (in the end):#ifndef SAVE_REPLAY_H#define SAVE_REPLAY_Hstruct SNAKE_STRUCT {    int Horizontal; // X    int Vertical;   // Y    int Direction;  // Last move    int Lives; // Lives left    bool Food; // Collected?    bool Crash;// Crashed?} SnakeState;class SaveReplay {    public:   void SaveState();   void WriteStateToFile(char Filename[MAX_PATH], SNAKE_STRUCT*);   void ViewReplay(char Filename[MAX_PATH]);    } Replay;void SaveReplay::SaveState() {    /* Fill the structure to write to the file */    SnakeState.Horizontal = Horizontal[0];    SnakeState.Vertical   = Vertical[0];    SnakeState.Direction  = LastMovement;    SnakeState.Lives = TotalLives;    SnakeState.Food  = FoodCollected;    SnakeState.Crash = Crashed;    return;}void SaveReplay::WriteStateToFile(char Filename[MAX_PATH], SNAKE_STRUCT *Snake_Rec) {    ofstream outFile(Filename, ios_base::out | ios_base::binary);    if (outFile.fail()) {   HANDLE hConsole = GetStdHandle(STD_INPUT_HANDLE);   SetConsoleDisplayMode(hConsole, 0, NULL); // FULL SCREEN = 1, WINDOWED MODE = 0   MessageBox(NULL, "Unable to write replay", "Error", MB_ICONERROR);   exit(0);    }    outFile.write((const char*)Snake_Rec, sizeof(SNAKE_STRUCT));    outFile.close();    return;}#endifIs what I have currently feasible, practical, good, bad? If there's room for improvement, please say so.Thanks in advance![Edited by - MuTeD on January 1, 2009 7:43:43 AM]
  Well you have a great start. The only thing I'm curious about is how much, exactly, do you want to replay? Do you want to replay to whole user-interface? If not, then there is no need for the lives, food, etc.Simplify your process drastically. To make your replay file as small as possible, which game developers still work on today in next-gen games, you want to calculate as much as you can on the fly.Now I am going to state a simple prequisite: only store the X and Y position. Everything else can be calculated on the fly.* Direction - Given the X and Y values every frame you can calculate the direction, if this is necessary. (Say you have actual sprites, not blocks, with the head facing in the different directions. Then this is necessary.)* Food - You MAY NEED to STORE this information. It all depends on how robust you want the implementation to be. Otherwise, I would just regenerate the level, and simulate it as the user goes. The drawback is that the user can go backwards in the replay, he has to restart from the start.Now if you want to make this robust, and you need to store this information, then please use a bit-array to represent whether or not Food was picked up. Meaning, record an array of all the locations of the food. Then create an array that contains enough bits to contain their state, 1 being not eaten and 0 being eaten. The position of the bit is the index into the location array.* Lives - Once again, this could be simulated from the start. But if this is a necessity because you need the UI, or your need support for backwards replay, then record it.* Crash - This should not be needed, regardless. This can be simulated given the level, the current position, and the position in the next frame. So this should be eliminated.If you have any questions/suspicions, then feel free to ask.   ;  First off: Thank you for the replySecond off: Thank you VERY much for making it meaningful and not just "u shuldnt do dat dis way," those type replys are very annoying and uselessThird off: It's in a console (meant for DOS), I do not use gaming libraries (DirectX, OpenGL, Allegro, etc)... It's pure ASCII Text, so there is no "sprites" the "blocks" I'd meant are asterisks (* is one "block")Fourth and finally: I had slapped that together, didn't put very much thought into it because I was unsure of myself, of whether or not I (all by myself) could do it, which in fact, I can!Here's what I have *now* and it works perfectly fine (feel free to criticize):#ifndef SAVE_REPLAY_H#define SAVE_REPLAY_Hstruct SNAKE_STRUCT {    int Direction;  // Last move    int SnakeX;// Snake - X    int SnakeY;// Snake - Y    int FoodX; // Food - X    int FoodY; // Food - Y} SnakeState, EmptySnakeState;class SaveReplay {    private:   char Filename[MAX_PATH];   void SaveState();    public:   SaveReplay(char File[MAX_PATH]);   void WriteState(char Filename[MAX_PATH], SNAKE_STRUCT*); /* Destructor puts an EMPTY structure to denote the replay end */   inline SaveReplay::~SaveReplay () {  ofstream outFile(Filename, ios_base::out | ios_base::binary | ios_base::app);  outFile.write((const char*)&EmptySnakeState, sizeof(SNAKE_STRUCT));  return;  }    } Replay("Replay.txt");    SaveReplay::SaveReplay(char File[MAX_PATH]) { strcpy(Filename, File); return; }void SaveReplay::SaveState() {    /* Fill the structure to write to the file */    SnakeState.Direction  = LastMovement;    SnakeState.SnakeX= Horizontal[0];    SnakeState.SnakeY= Vertical[0];    SnakeState.FoodX = FoodX;    SnakeState.FoodY = FoodY;    return;}void SaveReplay::WriteState(char File[MAX_PATH], SNAKE_STRUCT *Snake_Rec) {    SaveState();    ofstream outFile(File, ios_base::out | ios_base::binary | ios_base::app);    if (outFile.fail()) {   HANDLE hConsole = GetStdHandle(STD_INPUT_HANDLE);   SetConsoleDisplayMode(hConsole, 0, NULL); // FULL SCREEN = 1, WINDOWED MODE = 0   MessageBox(NULL, "Unable to write replay", "Error", MB_ICONERROR);   exit(0);    }    outFile.write((const char*)Snake_Rec, sizeof(SNAKE_STRUCT));    outFile.close();    return;}#endifHere's a jist of the replay system (what the intentions and objectives were):Quote:- User starts game  - User presses up arrow -> GAME STATE SAVED -> Appended to file  - User presses down arrow -> GAME STATE SAVED -> Appended to file  - User presses left arrow -> GAME STATE SAVED -> Appended to file  - User presses up arrow -> User crashes into a wall -> GAME STATE SAVED -> Appended to file  - User presses right arrow -> GAME STATE SAVED -> Appended to file- User quits game- User enters main menu- User starts a replay  - File read -> Current_SnakeState; File pointer moved; Next_SnakeState filled  - File read -> Current_SnakeState; File pointer moved; Next_SnakeState filled  - File read -> Current_SnakeState; File pointer moved; Next_SnakeState filled  - File read -> Current_SnakeState; File pointer moved; Next_SnakeState filled  - File read -> Current_SnakeState; File pointer moved; Next_SnakeState filled with nulls; Replay ends- User exits replay section- User terminates programThanks again for the reply. :)   ;  No problem, I'm glad you got it working! Great job. Just remember, if you always ask the question the right way, as you did above, then someone will always be there with a meaningful answer. (I hope, or else I'll be a liar. :D )   ;Quote:Original post by Halifax2No problem, I'm glad you got it working! Great job. Just remember, if you always ask the question the right way, as you did above, then someone will always be there with a meaningful answer. (I hope, or else I'll be a liar. :D )Thank you again, and I'll remember that.But, you have nothing more to say regarding the current method? No improvements, suggestions?   ;  When I've done this in the past, I've just cached the staring seed used for rand at the start of a level, then for each frame: the frametime; and the user input values. You need a slightly strange form of controller interface, eg:class IController {public:  virtual void Poll(FILE* fp)=0;  bool UpPressed() const { return buttons.up==1; }  bool DownPressed() const { return buttons.down==1; }  bool LeftPressed() const { return buttons.left==1; }  bool RightPressed() const { return buttons.right==1; }  bool XPressed() const { return buttons.x==1; }  bool YPressed() const { return buttons.y==1; }  bool APressed() const { return buttons.a==1; }  bool BPressed() const { return buttons.b==1; }protected:  struct BUTTONS {    uchar up:1;    uchar down:1;    uchar left:1;    uchar right:1;    uchar x:1;    uchar y:1;    uchar a:1;    uchar b:1;  } buttons;};Those values are basically booleans (you may need to add some floats for analog sticks), they can be stored as bit fields. Since there are 8 values above, that equates to a single byte per controller. Therefore each frame, the total data size is (sizeof(float) + numControllersUsed). Create 2 classes, eg:class ActualController : public IController {public:  virtual void Poll(FILE* fp)  {    // update bit fields from controller/keystates etc    // write to replay file    fwrite(&buttons,sizeof(BUTTONS),1,fp);  }};class ReplayController : public IController {public:  virtual void Poll(FILE* fp)  {    // read from replay file    fread(&buttons,sizeof(BUTTONS),1,fp);  }};Initialisation of the level is like so:// vector<IController*> gControllers;void InitLevel(FILE* replay_file,bool is_replaying){  // the random seed value  unsigned seed = 0;  // number of controllers connected  unsigned num_controllers = 0;  if(!is_replaying)  {    num_controllers = GetNumberOfConnectedControllers();    seed = timeGetTime();    fwrite(&seed,sizeof(unsigned),1,fp);    fwrite(&num_controllers,sizeof(unsigned),1,fp);    gControllers.resize(num_controllers);    for(unsigned i=0;i!=num_controllers;++i) gControllers = new ActualController;  }  else  {    fread(&seed,sizeof(unsigned),1,fp);    fread(&num_controllers,sizeof(unsigned),1,fp);    gControllers.resize(num_controllers);    for(unsigned i=0;i!=num_controllers;++i) gControllers = new ReplayController;  }  // set seed  srand(seed);}and the update is like so:// this func does your actual updateextern void doGameUpdate(float dt);void update(FILE* replay_file,bool is_replaying,float dt){  // do frame time  if(is_replaying)   fread(&dt,sizeof(float),1,fp);  else   fwrite(&dt,sizeof(float),1,fp);  // poll controllers  for(size_t i=0;i!=gControllers.size();++i)   gControllers->Poll(replay_file);  // update the game state   doGameUpdate(dt);}Assuming your game engine is deterministic (does the same thing every single time), then the user input is usually the only thing that actually drives your game engine (ignoring networking for a minute). This is why storing the random seed used at the beginning of the level is so important...  (If you want to be cross platform, then make sure you use your own RNG). The nice advantage of all this, is that it's easy to re-use this replay code for any game you write in future since it's not dependent on any game state....[Edited by - RobTheBloke on January 2, 2009 11:28:08 AM];Quote:Original post by RobTheBlokeWhen I've done this in the past, I've just cached the staring seed used for rand at the start of a level, then for each frame: the frametime; and the user input values. You need a slightly strange form of controller interface, eg:*** Source Snippet Removed ***Those values are basically booleans (you may need to add some floats for analog sticks), they can be stored as bit fields. Since there are 8 values above, that equates to a single byte per controller. Therefore each frame, the total data size is (sizeof(float) + numControllersUsed). Create 2 classes, eg:*** Source Snippet Removed ***Initialisation of the level is like so:*** Source Snippet Removed ***and the update is like so:*** Source Snippet Removed ***Assuming your game engine is deterministic (does the same thing every single time), then the user input is usually the only thing that actually drives your game engine (ignoring networking for a minute). This is why storing the random seed used at the beginning of the level is so important...  (If you want to be cross platform, then make sure you use your own RNG). The nice advantage of all this, is that it's easy to re-use this replay code for any game you write in future since it's not dependent on any game state....Thank you very! Very! Much for the code, and example, and heavy explaination of all that.. stuff, but. I cannot use it.I AM USING A COMMAND CONSOLE, THIS GAME DOES NOT USE SPRITES, IT IS PURE ASCII ART, IT IS NOT BUILT WITH A GAME LIBRARY OR LIBRARY OF ANY SORTPlease read the above that is in caps and bolded before posting, thanks! :)   ;Quote:Original post by MuTeDThank you very! Very! Much for the code, and example, and heavy explaination of all that.. stuff, but. I cannot use it.I AM USING A COMMAND CONSOLE, THIS GAME DOES NOT USE SPRITES, IT IS PURE ASCII ART, IT IS NOT BUILT WITH A GAME LIBRARY OR LIBRARY OF ANY SORTPlease read the above that is in caps and bolded before posting, thanks! :)So? RobTheBloke's answer is still valid for text games. Obviously your implementation of a controller interface might be different, but the principle's the same.   ;Quote:Original post by MuTeDI AM USING A COMMAND CONSOLE, THIS GAME DOES NOT USE SPRITES, IT IS PURE ASCII ART, IT IS NOT BUILT WITH A GAME LIBRARY OR LIBRARY OF ANY SORTComplete and utter BS. The code i posted has the exactly the same amount of library dependencies as the code you posted. It is no less valid than your solution. I assume you noticed this line:    // update bit fields from controller/keystates etcand it's up to you to replace that with the code to store the current state of your controls. If you happen to be using DInput/XInput/SDL/Glut/Win32 messaging or even scanf from the C std library - makes absolutely no difference. Just because the interface makes it look like a joypad - doesn't mean the implementation is. For future reference. Your code *does* depend on a library - namely the C++ std library. You asked for improvement advice for your design, and i provided a version that has smaller data requirements, is re-usable, requires less maintainence, and scales better.   ;Quote:Original post by RobTheBlokeYou asked for improvement advice for your design, and i provided a version that has smaller data requirements, is re-usable, requires less maintainence, and scales better.You're absolutely right, I did ask for advice and suggestions, I did not ask for you to rewrite my program for me in an OO manner.To do and complete somebodys project for them does not benefit them in any manner, it only harms them in the fact that they can not learn from it.I thank you for the advice, and I am kindfully discarding it, although it may be a perfectly legitiment and perfectly fine implemention (I'm sure there's hundreds of ways to go about it with any paradigm), I do not like it, I'm fine with mine, I simply wanted advice and suggestions on what I have for now.Between this post and the last I've personally changed it alot, and have had other suggestions such as: Saving the time-stamp and direction the Snake moved, along with the food's seed so that way I can just let the Snake play (as a replay from a saved file).That's not rewritting my program, it's simply advice (based on an assumption of how I wrote it) on how to compress it smaller. A couple of other suggestions I've had between now and then is using a <stack> or <vector>, and just simply pushing/popping them (which has its own benefits/negatives).Again, thank you very much for the advice, but I am choosing (in a non-insulting manner) to decline the implemention method, as I was not asking you to redesign or rewrite my program.And yes, I did see that one commented line, I did read what you wrote and it's fine.Quote:Original post by Mike nlSo? RobTheBloke's answer is still valid for text games. Obviously your implementation of a controller interface might be different, but the principle's the same.My implemention of the "controller interface," is of a non-OOP manner. It's quite different. I'm unsure of what you mean by "but the principle's the same," care to explain? :)   