/* Reset the game state, called prior to starting a new game. */ void reset_game(nbstate *state) { /* Start on the title screen, level 0: */ state->state = STATE_TITLESCREEN; state->level = 0; state->scores.s = 0; /* Start with a score of 0. */ state->numballs = state->startballs; /* Set the initial balls. */ park_ball(state); /* Park the current ball. */ /* Reset all the power-up and power-down timers: */ state->powertimes.widebat = 0; state->powertimes.slowmotion = 0; state->powertimes.stickybat = 0; state->powertimes.powerball = 0; state->powertimes.narrowbat = 0; state->powertimes.fastmotion = 0; /* Reset to the normal bat size: */ state->bat = NORMALBAT; /* Reset to the normal ball velocity: */ state->ball.v = state->ball.nv; /* Draw the title screen: */ set_level_active(state); }
/* Increment to the next level. */ void increment_level(nbstate *state) { /* If we are already on the last level: */ if(state->level == state->numlevels) { /* Go the the "game won" state. */ state->state = STATE_GAMEWON; /* Update the high score and save it if appropriate: */ save_hiscore(state); } else { /* Not on the last level. */ /* If we're on the title screen, go to the running state. */ if(state->state == STATE_TITLESCREEN) { state->state = STATE_RUNNING; /* Otherwise give the player some more balls for completing a * level: */ } else state->numballs += state->newlevelballs; state->level++; /* Increment the level counter. */ } /* We always start a new level with the ball parked: */ park_ball(state); /* Draw the new level and set up various other things for it: */ set_level_active(state); }
/* Called whenever a ball is lost, either by it falling off the bottom of the * screen, or the player pressing the "suicide key". */ void lost_ball(nbstate *state) { /* Decrement the balls count and if there are none left: */ if(!state->numballs--) { /* Go to the "game lost" state and update the high score if * appropriate: */ state->state = STATE_GAMELOST; /* Update the high score and save it if necessary: */ save_hiscore(state); /* This could probably be done better by just drawing the * splash- set_level_active() redraws the entire game area: */ set_level_active(state); return; } /* Erase the balls line at the top of the screen: */ draw_background(state, 0, state->scores.h, state->canvaswidth, state->ball.s->h + (BALLS_BORDER * 2)); /* Draw the balls again, but with one less than there was before: */ draw_balls(state); /* Copy the balls row to the output window: */ draw_canvas(state, 0, state->scores.h, state->canvaswidth, state->ball.s->h + (BALLS_BORDER * 2)); /* Park the new ball and erase the old one: */ park_ball(state); move_ball(state); /* Redraw the bat. This is a bit of a hack because sometimes when the * ball falls below the top of the bat on its way to the bottom of the * screen it can clip the bat and erase a bit of it. This redraws the * bat to hide that: */ draw_bat(state); /* Copy the redrawn bat to the output window: */ draw_canvas(state, state->batx - (state->batwidths[state->bat] / 2), state->canvasheight - state->batheight, state->batwidths[state->bat], state->batheight); }
/* Check if the ball will collide with something if it is moved to the * specified coordinates. If so, the direction is changed and 1 is returned * to indicate that the caller should recalculate the new coordinates and * try again. If there was no collision it returns 0. If something exceptional * happens (eg. the last brick is destroyed or the last ball is lost) it * returns 2 to indicate that the caller should give up trying to move the * ball. */ int check_ball_collision(nbstate *state, coords *c) { int i, bc; grid *g = state->grid; /* Check for a collision with the top of the game area: */ if(c->y < state->ball.s->h + (2 * BALLS_BORDER) + state->scores.h) { /* Bounce the ball back down and ask the caller to try again: */ state->ball.d = normalise_angle(M_PI - state->ball.d); return 1; } /* Check for a collision with the bottom of the game area: */ if(c->y > state->canvasheight - state->ball.s->h) { /* If the solidfloor cheat is active, bounce the ball back up * and ask the caller to try again: */ if(state->flags.sf) { state->ball.d = normalise_angle(M_PI - state->ball.d); return 1; } else { /* Otherwise destroy the ball, move the new ball to * the parked position (park_ball() is called by * lost_ball()) and ask the caller to give up trying * to move the ball: */ lost_ball(state); move_ball(state); return 2; } } /* Check for a collision with the left hand side of the game area: */ if(c->x < 0) { /* Bounce the ball back and ask the caller to try again: */ state->ball.d = normalise_angle((2 * M_PI) - state->ball.d); return 1; } /* Check for a collision with the right hand side of the game area: */ if(c->x > state->canvaswidth - state->ball.s->w) { /* Bounce the ball back and ask the caller to try again: */ state->ball.d = normalise_angle((2 * M_PI) - state->ball.d); return 1; } /* Check for a collision with the bat: */ if(c->y > state->canvasheight - state->batheight - state->ball.s->h && c->x > state->batx - (state->batwidths[state->bat] / 2) - state->ball.s->w && c->x < state->batx + (state->batwidths[state->bat] / 2)) { /* If the collision happened with the side of the bat instead * of the top, we don't care so just tell the caller there * was no collision: */ if(state->ball.y > state->canvasheight - state->batheight - state->ball.s->h) return 0; /* If the StickyBat power-up is active, park the ball: */ if(state->powertimes.stickybat) { park_ball(state); move_ball(state); return 2; } else { /* Otherwise bounce it back up and ask the caller to * try again: */ state->ball.d = normalise_angle(((c->x + (state->ball.s->w / 2) - state->batx) / state->batwidths[state->bat] / 2) * M_PI); return 1; } } /* Check for collisions with the bricks: */ bc = 0; /* No collisions have happened yet. */ /* For each brick in the grid: */ for(i = 0; i < state->width * state->height; i++) { /* If there is a brick in this grid position and the ball * intersects it: */ if(g->b && c->y + state->ball.s->h > g->y && c->y < g->y + state->brickheight && c->x + state->ball.s->w > g->x && c->x < g->x + state->brickwidth) { /* Perform the brick collision actions, and if * something exceptional happens (ie. we destroy the * last brick), return straight away asking the caller * to give up trying to move the ball: */ if(brick_collision(state, g)) return 2; /* Unless the NoBounce cheat is active, bounce the * ball off the brick. Only do this on the first brick * collision we find. */ if(!state->flags.nb && !bc) { bc = 1; /* Bounce off the left face: */ if(state->ball.x + state->ball.s->w < g->x) { state->ball.d = normalise_angle((2 * M_PI) - state->ball.d); /* Bounce off the right face: */ } else if(state->ball.x >= g->x + state->brickwidth) { state->ball.d = normalise_angle((2 * M_PI) - state->ball.d); /* Bounce off the upper face: */ } else if(state->ball.y + state->ball.s->h < g->y) { state->ball.d = normalise_angle(M_PI - state->ball.d); /* Bounce off the lower face: */ } else if(state->ball.y >= g->y + state->brickheight) { state->ball.d = normalise_angle(M_PI - state->ball.d); } else { /* This shouldn't happen, but I don't * trust the above algorithm 100%. */ debug_printf ("Internal error: " "couldn't figure out brick " "collision face\n"); } } } g++; /* Increment to the next grid position. */ } /* If a brick collision occured, ask the caller to try again: */ if(bc) return 1; return 0; /* Otherwise tell the caller that no collision occured. */ }