/* Increment the score by the specifed amount and redraw the score bar: */ void increment_score(nbstate *state, int points) { state->scores.s += points; /* Increment the current score. */ draw_scores(state); /* Redraw the score bar onto the canvas. */ /* Copy the bar onto the output window. */ draw_canvas(state, 0, 0, state->canvaswidth, state->scores.h); }
/* 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); }
/* Redraw the ball in its current position, including copying it to the output * window. */ void redraw_ball(nbstate *state) { /* Erase the ball area to the background: */ draw_background(state, (int)state->ball.x, (int)state->ball.y, state->ball.s->w, state->ball.s->h); /* Redraw any powers we may have erased: */ redraw_powers(state, (int)state->ball.x, (int)state->ball.y, state->ball.s->w, state->ball.s->h); /* Redraw the ball: */ draw_ball(state); /* Copy it to the output window: */ draw_canvas(state, state->ball.lx, state->ball.ly, state->ball.s->w, state->ball.s->h); }
/* Destroy the specified brick, clear the relevant piece of the game area, * increment the score by the appropriate amount, decrement the current * number of bricks, and if we run out of bricks, increment to the next level. * Returns 0 if the level wasn't incremented and 1 if it was. */ static int destroy_brick(nbstate *state, grid *g) { int immutable; /* Wipe the canvas where the brick is back to the background: */ draw_background(state, g->x, g->y, state->brickwidth, state->brickheight); /* Redraw any powers we may just have accidentally erased: */ redraw_powers(state, g->x, g->y, state->brickwidth, state->brickheight); /* Copy the affected area to the output window: */ draw_canvas(state, g->x, g->y, state->brickwidth, state->brickheight); /* Increment the score by the appropriate amount: */ if(g->b->flags & BRICK_FLAG_HUGE_BONUS) increment_score(state, state->hugebonuspoints); else if(g->b->flags & BRICK_FLAG_LARGE_BONUS) increment_score(state, state->largebonuspoints); else if(g->b->flags & BRICK_FLAG_MEDIUM_BONUS) increment_score(state, state->mediumbonuspoints); else if(g->b->flags & BRICK_FLAG_SMALL_BONUS) increment_score(state, state->smallbonuspoints); else increment_score(state, state->normalpoints); /* Remember whether this was an immutable brick (which does not factor * when counting the number of bricks remaining because it cannot * normally be destroyed- only the PowerBall power-up and the * NoBounce cheat can cause immutable bricks to be destroyed. */ immutable = g->b->flags & BRICK_FLAG_IMMUTABLE; g->b = 0; /* Wipe the brick from this grid location. */ /* If this wasn't an immutable brick, decrement the current count of * bricks and if we run out, increment to the next level and return. */ if(!immutable && !--state->numbricks) { increment_level(state); return 1; /* Level was incremented. */ } /* Create the power-up or power-down (if any): */ if(g->power != NOPOWER) new_power(state, g->power, g->x + (state->brickwidth / 2), g->y); return 0; /* Level wasn't incremented. */ }
/* Move the ball to from the position specified by state->ball.lx and * state->ball.ly to state->ball.x and state->ball.y, and update the relevant * area of the output window. */ void move_ball(nbstate *state) { int x, w, y, h; /* Check that the ball really has moved: */ if(state->ball.lx == state->ball.x && state->ball.ly == state->ball.y) return; /* Calculate the position and dimensions of the rectangle which * encloses both the old ball and the new ball so we know what area * to copy to the output window later. FIXME: this is quite inefficient * when doing stuff like parking the ball, where the old ball position * could be at the other side of the screen to the place we want to * move it to. It is however OK in the common case of only moving the * ball a few pixels at a time. */ if(state->ball.x < state->ball.lx) { x = (int)state->ball.x; w = state->ball.lx - x + state->ball.s->w; } else { x = state->ball.lx; w = (int)state->ball.x - x + state->ball.s->w; } if(state->ball.y < state->ball.ly) { y = (int)state->ball.y; h = state->ball.ly - y + state->ball.s->h; } else { y = state->ball.ly; h = (int)state->ball.y - y + state->ball.s->h; } /* Draw the background where the old ball image is to erase it: */ draw_background(state, state->ball.lx, state->ball.ly, state->ball.s->w, state->ball.s->h); /* Redraw any powers that are under that position: */ redraw_powers(state, state->ball.lx, state->ball.ly, state->ball.s->w, state->ball.s->h); /* Draw the ball in the new position and update ball.lx and ball.ly: */ draw_ball(state); /* Draw the modified area of the canvas to the output window: */ draw_canvas(state, x, y, w, h); }
/* Called from destroy_brick() to create a new power box of the specified type * centred on the specified location. */ void new_power(nbstate *state, int type, int x, int y) { power *p, *pn; sprite *s = state->powersprites[type]; /* Allocate the new power structure: */ if(!(p = malloc(sizeof(power)))) { oom(); return; } /* Fill in the parameters: */ p->type = type; p->y = y; /* Calculate the left hand coordinate of the power box (the passed in * X coordinate specifies the centre, not the actual position): */ p->x = x - (s->w / 2); /* Shift the box left or right if needed to make sure it is fully on * the screen: */ if(p->x < 0) p->x = 0; if(p->x + s->w > state->canvaswidth) p->x = state->canvaswidth - s->w; p->next = NULL; /* This is the last in the list so there is no next. */ /* If the list is empty, make this power the head of the list: */ if(!state->powers) state->powers = p; else { /* Otherwise loop through until the end of the list: */ for(pn = state->powers; pn->next; pn = pn->next); pn->next = p; /* Tag this power onto the end of the list. */ } /* Draw the new power: */ draw_power(state, p, p->x, p->y, s->w, s->h); /* Draw the modified area of the canvas to the output window: */ draw_canvas(state, p->x, p->y, s->w, s->h); }
/* Called whenever the ball collides with a brick, and handles such things as * destroying the brick if appropriate, redrawing it with more transparency * if appropriate, incrementing to the next level when we destroy the last brick * (actually, that's done by destroy_brick() which is called from * brick_collision()), etc. Returns 0 if the level was not incremented and 1 * if it was. */ int brick_collision(nbstate *state, grid *g) { /* If either the PowerBall power-up or the NoBounce cheat is active, * simply destroy the brick in one hit even if it is immutable. */ if(state->powertimes.powerball || state->flags.nb) return destroy_brick(state, g); /* If this is an immutable brick and PowerBall and NoBounce are not * active, it can't be destroyed so return normally: */ if(g->b->flags & BRICK_FLAG_IMMUTABLE) return 0; /* Increment the number of hits on this brick. This is used to keep * track of when to destroy a "2 hits or "3 hits" brick, and also how * transparent to draw those bricks as: */ g->hits++; /* If this is a 2-hit brick and it has only been hit once or it is a * 3 hit brick that has been hit either once or twice: */ if(((g->b->flags & BRICK_FLAG_2_HITS) && g->hits < 2) || ((g->b->flags & BRICK_FLAG_3_HITS) && g->hits < 3)) { /* Clear the area where the brick is: */ draw_background(state, g->x, g->y, state->brickwidth, state->brickheight); /* Redraw the brick (with additional transparency to indicate * that it has been hit): */ draw_brick(state, g, 0, 0, state->brickwidth, state->brickheight); /* Redraw any powers we may just have accidentally erased: */ redraw_powers(state, g->x, g->y, state->brickwidth, state->brickheight); /* Copy the changed area to the output window: */ draw_canvas(state, g->x, g->y, state->brickwidth, state->brickheight); /* Otherwise, destroy the brick: */ } else return destroy_brick(state, g); return 0; /* Level was not incremented. */ }
/* Because the canvas is a full double buffer, all we need to do when we get * an exposure event is to copy the specified area of the canvas to the * output window. */ void handle_exposure_event(nbstate *state, GR_EVENT_EXPOSURE *ev) { draw_canvas(state, ev->x, ev->y, ev->width, ev->height); }
/* Called every animation frame to move all of the power boxes down and perform * collision detection, etc. Doesn't draw the results to the canvas unless the * power collides with something and is destroyed. */ void animate_powers(nbstate *state) { sprite *s; power *p, *pnext; /* For each power in the list: */ for(p = state->powers; p; p = pnext) { /* Remember the next item first, because we may destroy this * power, and then it would be wrong to use the pointer to * find the pointer to the next one in the list: */ pnext = p->next; /* Find the sprite for this power type: */ s = state->powersprites[p->type]; /* Clear the old power away: */ draw_background(state, p->x, p->y, s->w, s->h); /* Redraw any bricks we may have accidentally erased: */ draw_bricks(state, p->x, p->y, s->w, s->h); /* Move it down: */ p->y += state->powerv; /* If it has reached the bottom of the screen: */ if(p->y + s->h > state->canvasheight) { /* Copy the erased region to the screen: */ draw_canvas(state, p->x, p->y - state->powerv, s->w, s->h); /* Destroy the power structure: */ destroy_power(state, p); /* Check if it will collide with the bat: */ } else if(p->y + s->h > state->canvasheight - state->batheight && p->x + s->w > state->batx - (state->batwidths[state->bat] / 2) && p->x < state->batx + (state->batwidths[state->bat] / 2)) { /* Copy the erased region to the screen: */ draw_canvas(state, p->x, p->y - state->powerv, s->w, s->h); /* Activate the relevant power: */ power_collision(state, p); /* Destroy the power structure: */ destroy_power(state, p); } else { /* Redraw it in its new location: */ draw_power(state, p, p->x, p->y, s->w, s->h); } } /* Redraw the ball in case we accidentally drew over it: */ redraw_ball(state); /* Copy all the modified areas to the output window: */ for(p = state->powers; p; p = p->next) draw_canvas(state, p->x, p->y - state->powerv, s->w, s->h + state->powerv); }
/* Make the current level active. This mainly consists of destroying the old * background image and loading the new one, destroying the splash image, * possibly loading a new splash image depending on the state, copying over the * new level data into the current level state, then redrawing the entire game * area. */ void set_level_active(nbstate *state) { int i; level *lev; grid *g, *gg; int bgchanged; sprite *s = NULL; power *p, *pnext; GR_PIXMAP_ID ctmp; char *backgroundfile; /* Destroy the old splash image sprite: */ destroy_sprite(state, state->splash); /* If we're on the title screen: */ if(state->state == STATE_TITLESCREEN) { /* Set the background file to the title background file: */ backgroundfile = state->titlebackground; /* Set the tiled state appropriately: */ state->backgroundtiled = state->titlebackgroundtiled; /* Try to load the title screen splash graphic (if it doesn't * work nothing bad will happen- load_sprite() will print an * error message and draw_splash() will not draw anything.) */ state->splash = load_sprite(state, state->titlesplash, -1, -1); } else { /* Not on the title screen. */ /* Find the level structure for the current level number: */ for(lev = state->levels, i = 1; i < state->level; lev = lev->next, i++); /* Set the current number of bricks and background info: */ state->numbricks = lev->numbricks; backgroundfile = lev->backgroundname; state->backgroundtiled = lev->backgroundtiled; /* If we're in the "game won" state, try to load the appropriate * splash image: */ if(state->state == STATE_GAMEWON) { state->splash = load_sprite(state, state->gamewonsplash, -1, -1); /* If we're in the "game lost" state, try to load the * appropriate splash image: */ } else if(state->state == STATE_GAMELOST) { state->splash = load_sprite(state, state->gamelostsplash, -1, -1); } else { /* We must be in the STATE_RUNNING state. */ /* No splash image: */ state->splash = NULL; /* Copy this levels game grid into the current game * grid: */ g = state->grid; gg = lev->grid; for(i = 0; i < state->width * state->height; i++) *g++ = *gg++; } } /* If there was a background filename specified: */ if(backgroundfile) { /* If there is a current background sprite with a filename * and the filename is the same as the new background * filename, the background file has not changed. Otherwise, * assume that it has. */ if(state->background && state->background->fname && !strcmp(backgroundfile, state->background->fname)) bgchanged = 0; else bgchanged = 1; /* No background filename was specified, so assume it has changed (to * a blank black frame): */ } else bgchanged = 1; /* If the background image has changed, try to load the new one: */ if(bgchanged && !(s = load_sprite(state, backgroundfile, -1, -1))) { /* If it fails, try to make a new empty sprite and colour it * in black (the 16*16 pixels is purely arbitrary- make it too * large and it uses a lot of memory, make it too small and * we spend ages painting hundreds of tiny tiles onto the * background. */ if(!(s = make_empty_sprite(state, backgroundfile, 16, 16))) { /* If that fails too (shouldn't happen under normal * circumstances), issue a warning and keep the old * background image sprite: */ s = state->background; } else { /* Fill in the new dummy background black: */ GrSetGCForeground(state->gc, GR_COLOR_BLACK); GrFillRect(s->p, state->gc, 0, 0, 16, 16); /* Make it tiled. FIXME: it would make more sense to * have a "no background image" option which simply * coloured in the background black: */ state->backgroundtiled = 1; } } /* If we have made a new background image sprite: */ if(bgchanged && s != state->background) { /* Destroy the old one: */ destroy_sprite(state, state->background); /* Set the background to the new sprite: */ state->background = s; } /* Empty the list of power boxes: */ for(p = state->powers; p; p = pnext) { pnext = p->next; free(p); } state->powers = NULL; /* If fading has been requested, we want to fade the new level in, so * swap the canvasses around so the current screen is the old canvas, * then draw the new screen and make the new canvas, then start the * process of fading it in: */ if(state->faderate) { /* Swap the canvasses around: */ ctmp = state->oldcanvas; state->oldcanvas = state->canvas; state->canvas = ctmp; /* Remember the state we're fading into: */ state->nextstate = state->state; /* Go into the fading state: */ state->state= STATE_FADING; /* Initialise the fade level as completely opaque: */ state->fadelevel = 256; } /* Clear the whole game area to the background image: */ draw_background(state, 0, 0, state->canvaswidth, state->canvasheight); /* If we're not on the title screen or fading to the title screen: */ if(state->state != STATE_TITLESCREEN && !(state->state == STATE_FADING && state->nextstate == STATE_TITLESCREEN)) { /* Draw the bricks: */ draw_bricks(state, 0, 0, state->canvaswidth, state->canvasheight); draw_scores(state); /* Draw the scores bar. */ draw_balls(state); /* Draw the row of balls. */ draw_bat(state); /* Draw the bat. */ /* Draw the current ball unless the game is over: */ if(state->state != STATE_GAMELOST && state->state != STATE_GAMEWON) draw_ball(state); } draw_splash(state); /* Draw the splash graphic (if there is one). */ /* If we're fading, remember the new canvas and generate the first * frame of the fade: */ if(state->state == STATE_FADING) { /* Swap the canvasses around: */ ctmp = state->newcanvas; state->newcanvas = state->canvas; state->canvas = ctmp; /* Reduce the opacity: */ state->fadelevel -= state->faderate; /* Generate the first frame: */ GrCopyArea(state->canvas, state->gc, 0, 0, state->canvaswidth, state->canvasheight, state->newcanvas, 0, 0, 0); GrCopyArea(state->canvas, state->gc, 0, 0, state->canvaswidth, state->canvasheight, state->oldcanvas, 0, 0, GR_CONST_BLEND | state->fadelevel); } /* Copy the entire redrawn canvas to the output window: */ draw_canvas(state, 0, 0, state->canvaswidth, state->canvasheight); }