Skip to content

Instantly share code, notes, and snippets.

@OswaldHurlem
Last active July 15, 2025 15:31
Show Gist options
  • Save OswaldHurlem/2a19e63760cba014b9884ff58205ea95 to your computer and use it in GitHub Desktop.
Save OswaldHurlem/2a19e63760cba014b9884ff58205ea95 to your computer and use it in GitHub Desktop.
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <assert.h>
struct lbp_serializer
{
int32_t DataVersion;
FILE* FilePtr;
bool IsWriting;
};
#define VERSION_IN_RANGE(_from, _to) \
(LbpSerializer->DataVersion >= (_from) && LbpSerializer->DataVersion < (_to))
#define ADD(_fieldAdded, _fieldName) \
if (LbpSerializer->DataVersion >= (_fieldAdded)) \
{ \
Serialize(LbpSerializer, &(Datum->_fieldName)); \
}
#define ADD_LOCAL(_localAdded, _type, _localName, _defaultValue) \
_type _localName = (_defaultValue); \
if (LbpSerializer->DataVersion >= (_localAdded)) \
{ \
Serialize(LbpSerializer, &(_localName)); \
}
#define REM(_fieldAdded, _fieldRemoved, _type, _fieldName, _defaultValue) \
_type _fieldName = (_defaultValue); \
if (VERSION_IN_RANGE((_fieldAdded),(_fieldRemoved))) \
{ \
Serialize(LbpSerializer, &(_fieldName)); \
}
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
void Serialize(lbp_serializer* LbpSerializer, int32_t* Datum)
{
if (LbpSerializer->IsWriting)
{
fwrite(Datum, sizeof(int32_t), 1, LbpSerializer->FilePtr);
}
else
{
fread(Datum, sizeof(int32_t), 1, LbpSerializer->FilePtr);
}
}
enum serialization_versions : int32_t
{
SV_Scores = 1,
SV_Fouls,
SV_ExtraPlayers,
SV_AllPlayersInList,
SV_FoulsUntracked,
// Keep this as the last element
SV_LatestPlus1,
};
struct pbem_pong_player
{
int32_t Points;
};
struct pbem_pong_player_list
{
int32_t Count;
pbem_pong_player* Ptr;
};
// (Dumb sample code) + (HMN STL Avoidance Cred) = (don't do this)
void Resize(pbem_pong_player_list* List, int32_t NewCount)
{
if (List->Count != NewCount)
{
if (!List->Ptr)
{
List->Ptr = (pbem_pong_player*)malloc(NewCount * sizeof(pbem_pong_player));
}
else
{
List->Ptr = (pbem_pong_player*)realloc(List->Ptr, NewCount * sizeof(pbem_pong_player));
}
assert(nullptr != List->Ptr);
for (int32_t i = List->Count; i < NewCount; i++)
{
List->Ptr[i] = {};
}
List->Count = NewCount;
}
}
// (Dumb sample code) + (HMN STL Avoidance Cred) = (don't do this)
void ResizeFromFront(pbem_pong_player_list* List, int32_t NewCount)
{
if (List->Count != NewCount)
{
int32_t Pad = NewCount - List->Count;
if (!List->Ptr)
{
List->Ptr = (pbem_pong_player*)malloc(NewCount * sizeof(pbem_pong_player));
}
else
{
List->Ptr = (pbem_pong_player*)realloc(List->Ptr, NewCount * sizeof(pbem_pong_player));
if (Pad > 0)
{
memmove(List->Ptr + Pad, List->Ptr, List->Count * sizeof(pbem_pong_player));
}
}
assert(nullptr != List->Ptr);
for (int32_t i = 0; i < Pad; i++)
{
List->Ptr[i] = {};
}
List->Count = NewCount;
}
}
void Free(pbem_pong_player_list* List)
{
free(List->Ptr);
List->Ptr = 0;
List->Count = 0;
}
struct pbem_pong_state
{
pbem_pong_player_list AllPlayers;
};
void Serialize(lbp_serializer* LbpSerializer, pbem_pong_player* Datum)
{
ADD(SV_ExtraPlayers, Points);
REM(SV_ExtraPlayers, SV_FoulsUntracked, int32_t, Fouls, 0);
Datum->Points = MAX(Datum->Points - Fouls, 0);
}
void Serialize(lbp_serializer* LbpSerializer, pbem_pong_player_list* Datum)
{
ADD_LOCAL(SV_ExtraPlayers, int32_t, NewCount, Datum->Count);
Resize(Datum, NewCount);
for (int32_t i = 0; i < Datum->Count; i++)
{
ADD(SV_ExtraPlayers, Ptr[i]);
}
}
const pbem_pong_player_list EmptyList = {};
void Serialize(lbp_serializer* LbpSerializer, pbem_pong_state* Datum)
{
REM(SV_Scores, SV_AllPlayersInList, int32_t, P1Score, 0);
REM(SV_Scores, SV_AllPlayersInList, int32_t, P2Score, 0);
REM(SV_Fouls, SV_AllPlayersInList, int32_t, P1Fouls, 0);
REM(SV_Fouls, SV_AllPlayersInList, int32_t, P2Fouls, 0);
REM(SV_ExtraPlayers, SV_AllPlayersInList, pbem_pong_player_list, ExtraPlayers, EmptyList);
ADD(SV_AllPlayersInList, AllPlayers);
if (VERSION_IN_RANGE(SV_Scores, SV_AllPlayersInList))
{
int32_t PlayersFromLegacyFormat = ExtraPlayers.Count + 2;
ResizeFromFront(&Datum->AllPlayers, Datum->AllPlayers.Count + PlayersFromLegacyFormat);
Datum->AllPlayers.Ptr[0].Points = MAX(P1Score - P1Fouls, 0);
Datum->AllPlayers.Ptr[1].Points = MAX(P2Score - P2Fouls, 0);
for (int i = 0; i < ExtraPlayers.Count; i++)
{
Datum->AllPlayers.Ptr[2 + i] = ExtraPlayers.Ptr[i];
}
Free(&ExtraPlayers);
}
}
void InitPongState(pbem_pong_state* PongState)
{
Resize(&PongState->AllPlayers, 2);
}
void Gameplay(pbem_pong_state* PongState)
{
Resize(&PongState->AllPlayers, PongState->AllPlayers.Count + 1);
printf("A dark figure emerges from the shadows and removes its cloak. It's Player %d.\n",
PongState->AllPlayers.Count);
for (int Ind = 0; Ind < PongState->AllPlayers.Count; Ind++)
{
pbem_pong_player* Player = PongState->AllPlayers.Ptr + Ind;
printf("Player %d has a score of %d\n", Ind+1, Player->Points);
if (rand() % 2)
{
printf("Player %d fouls! But maybe this will pay off?\n", Ind+1);
Player->Points = MAX(Player->Points - 1, 0);
}
else
{
printf("Player %d scores! But he'll need more than that to win.\n", Ind+1);
Player->Points++;
}
printf("Now Player %d has a score of %d\n", Ind+1, Player->Points);
}
}
bool SerializeIncludingVersion(lbp_serializer* LbpSerializer, pbem_pong_state* PongState)
{
if (LbpSerializer->IsWriting)
{
LbpSerializer->DataVersion = SV_LatestPlus1 - 1;
}
Serialize(LbpSerializer, &LbpSerializer->DataVersion);
if (LbpSerializer->DataVersion > (SV_LatestPlus1 - 1))
{
return false;
}
else
{
Serialize(LbpSerializer, PongState);
return true;
}
}
void main(int32_t, char**)
{
int32_t latestVersion = SV_LatestPlus1 - 1;
FILE* filePtr;
srand(time(NULL));
bool Success = false;
pbem_pong_state PongState = {};
while (!Success)
{
printf("Welcome to Pong By Email v%d.\nEnter the PBEM file you'd like to open, "
"or & to start a new game\n", latestVersion);
char buffer[256];
gets_s(buffer);
if (buffer[0] == '&')
{
InitPongState(&PongState);
Success = true;
}
else
{
if (0 == fopen_s(&filePtr, buffer, "r"))
{
lbp_serializer LbpSerializer;
LbpSerializer.IsWriting = false;
LbpSerializer.FilePtr = filePtr;
if (SerializeIncludingVersion(&LbpSerializer, &PongState))
{
printf("Opened PBEM file %s (from version %d)\n", buffer, LbpSerializer.DataVersion);
Success = true;
}
else
{
printf("Can't read PBEM file %s from version %d\n", buffer, LbpSerializer.DataVersion);
}
fclose(filePtr);
}
else
{
printf("Couldn't open %s\n", buffer);
}
}
}
Gameplay(&PongState);
Success = false;
while (!Success)
{
printf("Enter name of PBEM file to save\n");
char buffer[256];
gets_s(buffer);
if (0 == fopen_s(&filePtr, buffer, "w"))
{
lbp_serializer LbpSerializer;
LbpSerializer.IsWriting = true;
LbpSerializer.FilePtr = filePtr;
SerializeIncludingVersion(&LbpSerializer, &PongState);
fclose(filePtr);
Success = true;
}
else
{
printf("Couldn't open %s\n", buffer);
}
}
printf("Thanks for playing. Look forward to version %d, coming out soon!\n", SV_LatestPlus1);
}
@OswaldHurlem
Copy link
Author

(explanation forthcoming)

@namandixit
Copy link

namandixit commented Jan 16, 2025

Hi @OswaldHurlem, are there some operator overloading shenanigans going on in line 232 or is that just a comma operator? What does this do?

@OswaldHurlem
Copy link
Author

OswaldHurlem commented Jan 16, 2025

@namandixit it's a goof but I don't remember what my intention was. I'll check it out this weekend.

@OswaldHurlem
Copy link
Author

OswaldHurlem commented Jan 20, 2025

@namandixit Yes it's a mistake. I've updated the gist to correct it. As soon as time allows, I'll update the article it accompanies, and replace this gist with a new one that has a fresh commit history (since I use the commit history to demonstrate how one updates an "LPB Serialization" routine).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment