diff --git a/gamestate.c b/gamestate.c index 5af7dd5..92031f5 100644 --- a/gamestate.c +++ b/gamestate.c @@ -35,6 +35,23 @@ static struct Box *getbox(cpShape *shape) return (struct Box *)cpShapeGetUserData(shape); } +static struct Grid *find_empty_grid(struct GameState *gs) +{ + // @Robust better memory mgmt + struct Grid *empty_grid = NULL; + for (int ii = 0; ii < MAX_GRIDS; ii++) + { + if (gs->grids[ii].body == NULL) + { + empty_grid = &gs->grids[ii]; + break; + } + } + // @Robust cleanly fail when not enough grids + assert(empty_grid != NULL); + return empty_grid; +} + static int grid_num_boxes(struct Grid *g) { int to_return = 0; @@ -69,14 +86,187 @@ void grid_destroy(cpSpace *space, struct Grid *grid) grid->body = NULL; } +// removes boxe from grid, then ensures that the rule that grids must not have +// holes in them is applied. +// uses these forward declared serialization functions to duplicate a box +void ser_box(char **out, struct Box *b); +void des_box(char **in, struct Box *new_box, struct GameState *gs, struct Grid *g); static void grid_remove_box(cpSpace *space, struct Grid *grid, struct Box *box) { box_destroy(space, box); - if (grid_num_boxes(grid) == 0) + struct GameState *gs = (struct GameState *)cpSpaceGetUserData(space); + int num_boxes = grid_num_boxes(grid); + if (num_boxes == 0) { grid_destroy(space, grid); + return; } + if (num_boxes == 1) + return; + + // could be a gap between boxes in the grid, separate into multiple grids + + // goal: create list of "real grids" from this grid that have boxes which are + // ONLY connected horizontally and vertically. whichever one of these "real grids" + // has the most blocks stays the current grid, so + // if a player is inhabiting this ship it stays that ship. + // The other "real grids" are allocated as new grids + +#define MAX_SEPARATE_GRIDS 8 + struct Box *separate_grids[MAX_SEPARATE_GRIDS][MAX_BOXES_PER_GRID] = {0}; + int cur_separate_grid_index = 0; + int processed_boxes = 0; + + struct Box **biggest_separate_grid = separate_grids[0]; + int biggest_separate_grid_length = 0; + + // process all boxes into separate, but correctly connected, grids + while (processed_boxes < num_boxes) + { + // grab an unprocessed box, one not in separate_grids, to start the flood fill + struct Box *unprocessed = NULL; + for (int i = 0; i < MAX_BOXES_PER_GRID; i++) + { + SKIPNULL(grid->boxes[i].shape); + struct Box *cur = &grid->boxes[i]; + bool cur_has_been_processed = false; + for (int sep_i = 0; sep_i < MAX_SEPARATE_GRIDS; sep_i++) + { + for (int sep_box_i = 0; sep_box_i < MAX_BOXES_PER_GRID; sep_box_i++) + { + if (cur == separate_grids[sep_i][sep_box_i]) + { + cur_has_been_processed = true; + break; + } + } + if (cur_has_been_processed) + break; + } + if (!cur_has_been_processed) + { + unprocessed = cur; + break; + } + } + assert(unprocessed != NULL); + + // flood fill from this unprocessed box, adding each result to a new separate grid + // https://en.wikipedia.org/wiki/Flood_fill + struct Box **cur_separate_grid = separate_grids[cur_separate_grid_index]; + cur_separate_grid_index++; + int separate_grid_i = 0; + { + // queue stuff @Robust use factored datastructure + struct Box *Q[MAX_BOXES_PER_GRID] = {0}; + int Q_i = 0; + + Q[Q_i] = unprocessed; + Q_i++; + struct Box *N = NULL; + while (Q_i > 0) + { + N = Q[Q_i - 1]; + Q_i--; + if (true) // if node "inside", this is always true + { + cur_separate_grid[separate_grid_i] = N; + separate_grid_i++; + processed_boxes++; + + V2 cur_local_pos = box_local_pos(N); + const V2 dirs[] = { + (V2){.x = -1.0f, .y = 0.0f}, + (V2){.x = 1.0f, .y = 0.0f}, + (V2){.x = 0.0f, .y = 1.0f}, + (V2){.x = 0.0f, .y = -1.0f}, + }; + int num_dirs = sizeof(dirs) / sizeof(*dirs); + + for (int ii = 0; ii < num_dirs; ii++) + { + V2 dir = dirs[ii]; + // @Robust faster method, not O(N^2), of getting the box + // in the direction currently needed + V2 wanted_local_pos = V2add(cur_local_pos, V2scale(dir, BOX_SIZE)); + struct Box *box_in_direction = NULL; + for (int iii = 0; iii < MAX_BOXES_PER_GRID; iii++) + { + SKIPNULL(grid->boxes[iii].shape); + if (V2cmp(box_local_pos(&grid->boxes[iii]), wanted_local_pos, 0.01f)) + { + box_in_direction = &grid->boxes[iii]; + break; + } + } + if (box_in_direction != NULL) + { + // make sure not already added to the separate grid + bool already_in_separate_grid = false; + for (int sepgrid_i = 0; sepgrid_i < MAX_BOXES_PER_GRID; sepgrid_i++) + { + if (cur_separate_grid[sepgrid_i] == NULL) + break; // assumed to be end of the current separate grid list + if (cur_separate_grid[sepgrid_i] == box_in_direction) + { + already_in_separate_grid = true; + break; + } + } + if (!already_in_separate_grid) + { + Q[Q_i] = box_in_direction; + Q_i++; + } + } + } + } + } + } + + if (separate_grid_i > biggest_separate_grid_length) + { + biggest_separate_grid_length = separate_grid_i; + biggest_separate_grid = cur_separate_grid; + } + } + + float ang_vel_per_mass = cpBodyGetAngularVelocity(grid->body) / cpBodyGetMass(grid->body); + + // create new grids for all lists of boxes except for the biggest one. + // delete the boxes out of the current grid as I pull boxes into separate ones + // which are no longer connected + for (int sepgrid_i = 0; sepgrid_i < MAX_SEPARATE_GRIDS; sepgrid_i++) + { + if (separate_grids[sepgrid_i] == biggest_separate_grid) + continue; // leave the boxes of the biggest separate untouched + struct Box **cur_separate_grid = separate_grids[sepgrid_i]; + int cur_sepgrid_i = 0; + if (cur_separate_grid[cur_sepgrid_i] == NULL) + continue; // this separate grid is empty + + struct Grid *new_grid = find_empty_grid(gs); + grid_new(new_grid, gs, grid_pos(grid)); // all grids have same pos but different center of mass (com) + cpBodySetAngle(new_grid->body, grid_rotation(grid)); + cpBodySetVelocity(new_grid->body, cpBodyGetVelocity(grid->body)); + int new_grid_box_i = 0; + while (cur_separate_grid[cur_sepgrid_i] != NULL) + { + char box_bytes[128]; + char * cur = box_bytes; + // duplicate the box by serializing it then deserializing it + ser_box(&cur, cur_separate_grid[cur_sepgrid_i]); + box_destroy(space, cur_separate_grid[cur_sepgrid_i]); + cur = box_bytes; + des_box(&cur,&new_grid->boxes[new_grid_box_i],gs, new_grid); + + cur_sepgrid_i++; + new_grid_box_i++; + } + cpBodySetAngularVelocity(new_grid->body, ang_vel_per_mass * cpBodyGetMass(new_grid->body)); + } + cpBodySetAngularVelocity(grid->body, ang_vel_per_mass * cpBodyGetMass(grid->body)); } static void postStepRemove(cpSpace *space, void *key, void *data) @@ -111,6 +301,7 @@ static cpBool on_damage(cpArbiter *arb, cpSpace *space, cpDataPointer userData) void initialize(struct GameState *gs) { gs->space = cpSpaceNew(); + cpSpaceSetUserData(gs->space, (cpDataPointer)gs); // needed in the handler cpCollisionHandler *handler = cpSpaceAddCollisionHandler(gs->space, 0, 0); // @Robust limit collision type to just blocks that can be damaged // handler->beginFunc = begin; handler->postSolveFunc = on_damage; @@ -214,11 +405,17 @@ float grid_angular_velocity(struct Grid *grid) { return cpBodyGetAngularVelocity(grid->body); } +struct Grid *box_grid(struct Box *box) +{ + return (struct Grid *)cpBodyGetUserData(cpShapeGetBody(box->shape)); +} +V2 box_local_pos(struct Box *box) +{ + return cp_to_v2(cpShapeGetCenterOfGravity(box->shape)); +} V2 box_pos(struct Box *box) { - struct Grid *g = (struct Grid *)cpBodyGetUserData(cpShapeGetBody(box->shape)); - V2 local_pos = cp_to_v2(cpShapeGetCenterOfGravity(box->shape)); - return V2add(grid_pos(g), V2rotate(local_pos, grid_rotation(g))); + return V2add(grid_pos(box_grid(box)), V2rotate(box_local_pos(box), grid_rotation(box_grid(box)))); } float box_rotation(struct Box *box) { @@ -303,6 +500,30 @@ void des_V2(char **in, V2 *v) des_float(in, &v->y); } +void ser_box(char **out, struct Box *b) +{ + ser_V2(out, cp_to_v2(cpShapeGetCenterOfGravity(b->shape))); + + ser_int(out, b->type); // @Robust separate enum serialization that checks for out of bounds enum + ser_int(out, b->rotation); + ser_float(out, b->thrust); + ser_float(out, b->energy_used); + ser_float(out, b->damage); +} + +void des_box(char **in, struct Box *new_box, struct GameState *gs, struct Grid *g) +{ + V2 pos = {0}; + des_V2(in, &pos); + box_new(new_box, gs, g, pos); + + des_int(in, (int *)&new_box->type); + des_int(in, (int *)&new_box->rotation); + des_float(in, &new_box->thrust); + des_float(in, &new_box->energy_used); + des_float(in, &new_box->damage); +} + void ser_grid(char **out, struct Grid *g) { // grid must not be null, dummy! @@ -321,13 +542,7 @@ void ser_grid(char **out, struct Grid *g) ser_bool(out, exists); if (exists) { - ser_V2(out, cp_to_v2(cpShapeGetCenterOfGravity(g->boxes[i].shape))); - - ser_int(out, g->boxes[i].type); // @Robust separate enum serialization that checks for out of bounds enum - ser_int(out, g->boxes[i].rotation); - ser_float(out, g->boxes[i].thrust); - ser_float(out, g->boxes[i].energy_used); - ser_float(out, g->boxes[i].damage); + ser_box(out, &g->boxes[i]); } } } @@ -354,21 +569,14 @@ void des_grid(char **in, struct Grid *g, struct GameState *gs) des_float(in, &g->total_energy_capacity); + // iterate over every box like this so box index is preserved on deserialization for (int i = 0; i < MAX_BOXES_PER_GRID; i++) { bool exists = false; des_bool(in, &exists); if (exists) { - V2 pos = {0}; - des_V2(in, &pos); - box_new(&g->boxes[i], gs, g, pos); - - des_int(in, (int *)&g->boxes[i].type); - des_int(in, (int *)&g->boxes[i].rotation); - des_float(in, &g->boxes[i].thrust); - des_float(in, &g->boxes[i].energy_used); - des_float(in, &g->boxes[i].damage); + des_box(in, &g->boxes[i], gs, g); } } } @@ -691,7 +899,7 @@ void process(struct GameState *gs, float dt) float needed_energy = wanted_thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt; energy_available -= needed_energy; - if(energy_available > 0.0f) + if (energy_available > 0.0f) g->boxes[ii].thrust = wanted_thrust; else g->boxes[ii].thrust = 0.0f; @@ -724,18 +932,7 @@ void process(struct GameState *gs, float dt) } else if (p->input.grid_index == -1) { - // @Robust better memory mgmt - struct Grid *empty_grid = NULL; - for (int ii = 0; ii < MAX_GRIDS; ii++) - { - if (gs->grids[ii].body == NULL) - { - empty_grid = &gs->grids[ii]; - break; - } - } - // @Robust cleanly fail when not enough grids - assert(empty_grid != NULL); + struct Grid *empty_grid = find_empty_grid(gs); p->spice_taken_away += 0.2f; grid_new(empty_grid, gs, world_build); box_new(&empty_grid->boxes[0], gs, empty_grid, (V2){0}); @@ -820,9 +1017,9 @@ void process(struct GameState *gs, float dt) } gs->grids[i].total_energy_capacity = 0.0f; - for(int ii = 0; ii < batteries_len; ii++) + for (int ii = 0; ii < batteries_len; ii++) { - gs->grids[i].total_energy_capacity += 1.0f - batteries[ii]->energy_used; + gs->grids[i].total_energy_capacity += 1.0f - batteries[ii]->energy_used; } } diff --git a/types.h b/types.h index c09f690..e53633e 100644 --- a/types.h +++ b/types.h @@ -131,11 +131,11 @@ struct GameState struct Box { enum BoxType type; - enum Rotation rotation; + enum Rotation rotation; // @Robust rename to compass_rotation // thruster float thrust; // must be between 0 and 1 - + // battery float energy_used; // must be between 0 and 1 @@ -208,6 +208,7 @@ V2 grid_snapped_box_pos(struct Grid *grid, V2 world); // returns the snapped pos float grid_rotation(struct Grid *grid); float grid_angular_velocity(struct Grid *grid); void box_new(struct Box *to_modify, struct GameState *gs, struct Grid *grid, V2 pos); +V2 box_local_pos(struct Box *box); V2 box_pos(struct Box *box); float box_rotation(struct Box *box);