/*
* Atomiks -- a modern version of the 1990 Atomix logic game
* Copyright (C) 2013 Mateusz Viste
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <time.h>
#include <SDL/SDL.h> /* SDL */
#include <SDL/SDL_image.h> /* SDL_image */
#include <SDL/SDL_mixer.h> /* SDL_mixer */
#include "atomcore.h"
#include "data.h"
#include "scale2x.h"
#include "cfg.h"
#define PVER "v1.03"
struct spritesstruct {
SDL_Surface *atom[49];
SDL_Surface *satom[49];
SDL_Surface *wall[19];
SDL_Surface *explosion[8];
SDL_Surface *empty;
SDL_Surface *preview;
SDL_Surface *bg[3];
SDL_Surface *completed;
SDL_Surface *cursor[4];
SDL_Surface *font1[37];
SDL_Surface *font2[11];
SDL_Surface *font3[26];
};
struct soundsstruct {
Mix_Chunk *selected;
Mix_Chunk *bzzz;
Mix_Chunk *explode;
int soundflag;
};
/* wait until a specific time. returns a 'late' flag - 0 if any wait time occured, non-zero if there was no time to wait */
static void wait_until_tick(Uint32 tick) {
while (SDL_GetTicks() < tick) SDL_Delay(1);
}
void draw_playfield_tile(struct atomixgame *game, int x, int y, struct spritesstruct *sprites, SDL_Surface *screen, SDL_Surface *tile) {
if (tile == NULL) {
if ((game->field[x][y] & field_type) == field_atom) {
tile = sprites->atom[game->field[x][y] & field_index];
} else if ((game->field[x][y] & field_type) == field_wall) {
tile = sprites->wall[game->field[x][y] & field_index];
} else if ((game->field[x][y] & field_type) == field_free) {
tile = sprites->empty;
} else { /* empty space */
tile = NULL;
}
}
if (tile != NULL) {
SDL_Rect rect;
rect.x = game->offseth + (x * sprites->empty->w);
rect.y = game->offsetv + (y * sprites->empty->h);
rect.w = sprites->empty->w;
rect.h = sprites->empty->h;
SDL_BlitSurface(sprites->empty, NULL, screen, &rect);
SDL_BlitSurface(tile, NULL, screen, &rect);
}
}
void draw_anim_explosions(struct atomixgame *game, struct spritesstruct *sprites, SDL_Surface *screen, struct soundsstruct *sounds) {
int listlen = 0;
int listx[64];
int listy[64];
int i, x, y;
/* compute the list of atoms on the playfield */
for (y = 0; y < game->field_height; y++) {
for (x = 0; x < game->field_width; x++) {
if ((game->field[x][y] & field_type) == field_atom) {
listx[listlen] = x;
listy[listlen] = y;
if (listlen < 62) listlen++;
}
}
}
if (listlen > 1) {
/* randomize the list */
srand(time(NULL));
for (i = 0; i < listlen; i++) { /* randomize as many times as many items we have in the list */
x = (rand() % listlen);
if (x != i) {
listx[63] = listx[i];
listy[63] = listy[i];
listx[i] = listx[x];
listy[i] = listy[x];
listx[x] = listx[63];
listy[x] = listy[63];
}
}
}
/* animate explosions of atoms */
for (x = 0; x < listlen; x++) {
if (sounds->soundflag != 0) Mix_PlayChannel(-1, sounds->explode, 0);
for (i = 0; i < 8; i++) {
draw_playfield_tile(game, listx[x], listy[x], sprites, screen, sprites->empty);
draw_playfield_tile(game, listx[x], listy[x], sprites, screen, sprites->explosion[i]);
SDL_Flip(screen);
SDL_Delay(40);
}
game->field[listx[x]][listy[x]] = field_free; /* update the playfield to mark the area free */
draw_playfield_tile(game, listx[x], listy[x], sprites, screen, sprites->empty);
SDL_Flip(screen);
SDL_Delay(150);
}
}
/* moves a tile from one position on the field to another */
void move_tile(struct atomixgame *game, int x_from, int y_from, int x_to, int y_to, SDL_Surface *screen, SDL_Surface *empty, SDL_Surface *atom, SDL_Surface *cursor, struct soundsstruct *sounds) {
int x, y, kierunekx = 0, kieruneky = 0, sndchannel;
SDL_Rect rect, refreshrect;
if (x_from > x_to) {
kierunekx = -1;
refreshrect.x = game->offseth + (x_to * 32);
refreshrect.y = game->offsetv + (y_from * 32);
refreshrect.w = ((x_from * 32) - (x_to * 32)) + 32;
refreshrect.h = 32;
} else if (x_from < x_to) {
kierunekx = 1;
refreshrect.x = game->offseth + (x_from * 32);
refreshrect.y = game->offsetv + (y_from * 32);
refreshrect.w = ((x_to * 32) - (x_from * 32)) + 32;
refreshrect.h = 32;
} else if (y_from > y_to) {
kieruneky = -1;
refreshrect.x = game->offseth + (x_from * 32);
refreshrect.y = game->offsetv + (y_to * 32);
refreshrect.w = 32;
refreshrect.h = ((y_from * 32) - (y_to * 32)) + 32;
} else if (y_from < y_to) {
kieruneky = 1;
refreshrect.x = game->offseth + (x_from * 32);
refreshrect.y = game->offsetv + (y_from * 32);
refreshrect.w = 32;
refreshrect.h = ((y_to * 32) - (y_from * 32)) + 32;
} else { /* we're not moving at all! */
return;
}
x = game->offseth + (x_from * 32);
y = game->offsetv + (y_from * 32);
rect.x = 0;
if (sounds->soundflag != 0) {
sndchannel = Mix_PlayChannel(-1, sounds->bzzz, -1);
} else {
sndchannel = -1;
}
for (; y != game->offsetv + (y_to * 32); y += kieruneky) {
if (rect.x != 0) SDL_BlitSurface(empty, NULL, screen, &rect);
rect.x = x;
rect.y = y;
rect.w = atom->w;
rect.h = atom->h;
if (y % 6 == 0) {
SDL_BlitSurface(atom, NULL, screen, &rect);
SDL_BlitSurface(cursor, NULL, screen, &rect);
SDL_UpdateRect(screen, refreshrect.x, refreshrect.y, refreshrect.w, refreshrect.h);
SDL_Delay(20);
}
}
rect.x = 0;
for (; x != game->offseth + (x_to * 32); x += kierunekx) {
if (rect.x != 0) SDL_BlitSurface(empty, NULL, screen, &rect);
rect.x = x;
rect.y = y;
rect.w = atom->w;
rect.h = atom->h;
if (x % 6 == 0) {
SDL_BlitSurface(empty, NULL, screen, &rect);
SDL_BlitSurface(atom, NULL, screen, &rect);
SDL_BlitSurface(cursor, NULL, screen, &rect);
SDL_UpdateRect(screen, refreshrect.x, refreshrect.y, refreshrect.w, refreshrect.h);
SDL_Delay(20);
}
}
if (sndchannel != -1) Mix_FadeOutChannel(sndchannel, 100);
game->field[x_to][y_to] = game->field[x_from][y_from];
game->field[x_from][y_from] = field_free;
}
/* moves the cursor up by one place up */
void move_cursor_up(struct atomixgame *game, SDL_Surface *screen, struct spritesstruct *sprites) {
int y, x, y1, y2;
Uint32 sleepuntilticks;
SDL_Rect rect;
if (game->cursory <= 0) return;
if (game->field[game->cursorx][game->cursory - 1] == 0) return;
x = game->offseth + (game->cursorx * 32);
y1 = game->offsetv + (game->cursory * 32);
y2 = game->offsetv + ((game->cursory - 1) * 32);
sleepuntilticks = SDL_GetTicks();
for (y = y1; y >= y2; y--) {
if (y % 4 == 0) {
sleepuntilticks += 10;
rect.x = x;
rect.y = y;
rect.w = sprites->empty->w;
rect.h = sprites->empty->h;
draw_playfield_tile(game, game->cursorx, game->cursory, sprites, screen, NULL);
draw_playfield_tile(game, game->cursorx, game->cursory - 1, sprites, screen, NULL);
SDL_BlitSurface(sprites->cursor[0], NULL, screen, &rect);
SDL_UpdateRect(screen, x, y2, sprites->empty->w, sprites->empty->h * 2);
wait_until_tick(sleepuntilticks);
}
}
game->cursory -= 1;
}
/* moves the cursor by one place right */
void move_cursor_right(struct atomixgame *game, SDL_Surface *screen, struct spritesstruct *sprites) {
int y, x, x1, x2;
Uint32 sleepuntilticks;
SDL_Rect rect;
if (game->field[game->cursorx + 1][game->cursory] == 0) return;
y = game->offsetv + (game->cursory * 32);
x1 = game->offseth + (game->cursorx * 32);
x2 = game->offseth + ((game->cursorx + 1) * 32);
sleepuntilticks = SDL_GetTicks();
for (x = x1; x <= x2; x++) {
if (x % 4 == 0) {
sleepuntilticks += 10;
rect.x = x;
rect.y = y;
rect.w = sprites->empty->w;
rect.h = sprites->empty->h;
draw_playfield_tile(game, game->cursorx, game->cursory, sprites, screen, NULL);
draw_playfield_tile(game, game->cursorx + 1, game->cursory, sprites, screen, NULL);
SDL_BlitSurface(sprites->cursor[0], NULL, screen, &rect);
SDL_UpdateRect(screen, x1, y, sprites->empty->w * 2, sprites->empty->h);
wait_until_tick(sleepuntilticks);
}
}
game->cursorx += 1;
}
/* moves the cursor by one place down */
void move_cursor_down(struct atomixgame *game, SDL_Surface *screen, struct spritesstruct *sprites) {
int y, x, y1, y2;
Uint32 sleepuntilticks;
SDL_Rect rect;
if (game->field[game->cursorx][game->cursory + 1] == 0) return;
x = game->offseth + (game->cursorx * 32);
y1 = game->offsetv + (game->cursory * 32);
y2 = game->offsetv + ((game->cursory + 1) * 32);
sleepuntilticks = SDL_GetTicks();
for (y = y1; y <= y2; y++) {
if (y % 4 == 0) {
sleepuntilticks += 10;
rect.x = x;
rect.y = y;
rect.w = sprites->empty->w;
rect.h = sprites->empty->h;
draw_playfield_tile(game, game->cursorx, game->cursory, sprites, screen, NULL);
draw_playfield_tile(game, game->cursorx, game->cursory + 1, sprites, screen, NULL);
SDL_BlitSurface(sprites->cursor[0], NULL, screen, &rect);
SDL_UpdateRect(screen, x, y1, sprites->empty->w, sprites->empty->h * 2);
wait_until_tick(sleepuntilticks);
}
}
game->cursory += 1;
}
/* moves the cursor up by one place left */
void move_cursor_left(struct atomixgame *game, SDL_Surface *screen, struct spritesstruct *sprites) {
int y, x, x1, x2;
Uint32 sleepuntilticks;
SDL_Rect rect;
if (game->cursorx <= 0) return;
if (game->field[game->cursorx - 1][game->cursory] == 0) return;
y = game->offsetv + (game->cursory * 32);
x1 = game->offseth + (game->cursorx * 32);
x2 = game->offseth + ((game->cursorx - 1) * 32);
sleepuntilticks = SDL_GetTicks();
for (x = x1; x >= x2; x--) {
if (x % 4 == 0) {
sleepuntilticks += 10;
rect.x = x;
rect.y = y;
rect.w = sprites->empty->w;
rect.h = sprites->empty->h;
draw_playfield_tile(game, game->cursorx, game->cursory, sprites, screen, NULL);
draw_playfield_tile(game, game->cursorx - 1, game->cursory, sprites, screen, NULL);
SDL_BlitSurface(sprites->cursor[0], NULL, screen, &rect);
SDL_UpdateRect(screen, x2, y, sprites->empty->w * 2, sprites->empty->h);
wait_until_tick(sleepuntilticks);
}
}
game->cursorx -= 1;
}
static void loadGraphic(SDL_Surface **surface, void *memptr, int memlen) {
SDL_RWops *rwop;
rwop = SDL_RWFromMem(memptr, memlen);
*surface = IMG_LoadPNG_RW(rwop);
SDL_FreeRW(rwop);
}
static void loadMusic(Mix_Music **musptr, void *memptr, int memlen) {
SDL_RWops *rwop;
rwop = SDL_RWFromMem(memptr, memlen);
*musptr = Mix_LoadMUS_RW(rwop);
SDL_FreeRW(rwop);
}
static void loadSnd(Mix_Chunk **chunkptr, void *memptr, int memlen) {
SDL_RWops *rwop;
rwop = SDL_RWFromMem(memptr, memlen);
*chunkptr = Mix_LoadWAV_RW(rwop, 0);
SDL_FreeRW(rwop);
}
static void loadSpriteSheet(SDL_Surface **surface, int width, int height, int itemcount, void *memptr, int memlen) {
SDL_Surface *spritesheet, *item;
SDL_Rect rect;
int i;
loadGraphic(&spritesheet, memptr, memlen);
item = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_SRCALPHA, width, height, 32, 0xFF000000L, 0x00FF0000L, 0x0000FF00L, 0x000000FFL);
for (i = 0; i < itemcount; i++) {
rect.x = i * width;
rect.y = 0;
rect.w = width;
rect.h = height;
SDL_FillRect(item, NULL, 0);
SDL_BlitSurface(spritesheet, &rect, item, NULL);
surface[i] = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_SRCALPHA, width << 1, height << 1, 32, 0xFF000000L, 0x00FF0000L, 0x0000FF00L, 0x000000FFL);
scale2x(item, surface[i]);
}
SDL_FreeSurface(item);
SDL_FreeSurface(spritesheet);
}
/* reads an ascii char and returns the index of the related glyph within font1 (A..Z0..9) */
static int ascii2font1(char c) {
if ((c >= 'A') && (c <= 'Z')) return(c - 'A');
if ((c >= 'a') && (c <= 'z')) return(c - 'a');
if ((c >= '0') && (c <= '9')) return(26 + (c - '0'));
return(36);
}
void draw_game_screen(struct atomixgame *game, struct spritesstruct *sprites, SDL_Surface *screen, int skipcursor, time_t curtime) {
int x, y;
SDL_Rect rect;
SDL_Surface *tile;
unsigned int timeleft = 0;
unsigned char font1_width[] = {5,5,4,5,4,4,5,5,2,4,4,4,6,5,5,5,5,5,5,4,5,4,6,4,5,4,5,4,4,4,4,4,5,4,5,5}; /* provides the width of every single glyph in the font1 set */
char tmpstring[16];
SDL_BlitSurface(sprites->bg[game->bg], NULL, screen, NULL); /* draw background */
if (curtime <= game->time_end) {
timeleft = game->time_end - curtime;
}
/* Draw the playfield */
for (y = 0; y < game->field_height; y++) {
for (x = 0; x < game->field_width; x++) {
draw_playfield_tile(game, x, y, sprites, screen, NULL);
}
}
/* draw the cursor */
if (skipcursor == 0) {
rect.x = game->offseth + (game->cursorx * sprites->cursor[0]->w);
rect.y = game->offsetv + (game->cursory * sprites->cursor[0]->h);
rect.w = sprites->cursor[0]->w;
rect.h = sprites->cursor[0]->h;
SDL_BlitSurface(sprites->cursor[game->cursorstate], NULL, screen, &rect);
}
/* Draw text ("HISCORE") */
rect.x = 16;
rect.y = 16;
rect.w = sprites->font2[0]->w;
rect.h = sprites->font2[0]->h;
SDL_BlitSurface(sprites->font3['H' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['I' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['S' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['C' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['O' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['R' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['E' - 'A'], NULL, screen, &rect);
/* Draw text (actual hiscore) */
rect.y += sprites->font3[0]->h * 1.40;
rect.x = 16;
rect.w = sprites->font2[0]->w;
rect.h = sprites->font2[0]->h;
snprintf(tmpstring, 16, "%d", game->hiscore);
for (x = 0; tmpstring[x] != 0; x++) {
SDL_BlitSurface(sprites->font2[(unsigned)tmpstring[x] - '0'], NULL, screen, &rect);
rect.x += sprites->font2[0]->w;
}
/* Draw text ("SCORE") */
rect.x = 16;
rect.y += 62;
rect.w = sprites->font2[0]->w;
rect.h = sprites->font2[0]->h;
SDL_BlitSurface(sprites->font3['S' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['C' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['O' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['R' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['E' - 'A'], NULL, screen, &rect);
/* Draw text (actual score) */
rect.y += sprites->font3[0]->h * 1.40;
rect.x = 16;
rect.w = sprites->font2[0]->w;
rect.h = sprites->font2[0]->h;
snprintf(tmpstring, 16, "%d", game->score);
for (x = 0; tmpstring[x] != 0; x++) {
SDL_BlitSurface(sprites->font2[(unsigned)tmpstring[x] - '0'], NULL, screen, &rect);
rect.x += sprites->font2[0]->w;
}
/* Draw text ("LEVEL") */
rect.x = 16;
rect.y += 62;
rect.w = sprites->font2[0]->w;
rect.h = sprites->font2[0]->h;
SDL_BlitSurface(sprites->font3['L' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['E' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['V' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['E' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['L' - 'A'], NULL, screen, &rect);
/* Draw text (level number) */
rect.y += sprites->font3[0]->h * 1.40;
rect.x = 16;
rect.w = sprites->font2[0]->w;
rect.h = sprites->font2[0]->h;
SDL_BlitSurface(sprites->font2[game->level / 10], NULL, screen, &rect);
rect.x += sprites->font2[0]->w;
SDL_BlitSurface(sprites->font2[game->level % 10], NULL, screen, &rect);
/* Draw text ("TIME") */
rect.x = 16;
rect.y += 62;
rect.w = sprites->font2[0]->w;
rect.h = sprites->font2[0]->h;
SDL_BlitSurface(sprites->font3['T' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['I' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['M' - 'A'], NULL, screen, &rect);
rect.x += sprites->font3[0]->w;
SDL_BlitSurface(sprites->font3['E' - 'A'], NULL, screen, &rect);
/* Draw text (time left) */
rect.x = 16;
rect.y += sprites->font3[0]->h * 1.40;
rect.w = sprites->font2[0]->w;
rect.h = sprites->font2[0]->h;
SDL_BlitSurface(sprites->font2[timeleft / 60], NULL, screen, &rect);
rect.x += sprites->font2[0]->w;
SDL_BlitSurface(sprites->font2[10], NULL, screen, &rect);
rect.x += sprites->font2[0]->w;
SDL_BlitSurface(sprites->font2[(timeleft % 60) / 10], NULL, screen, &rect);
rect.x += sprites->font2[0]->w;
SDL_BlitSurface(sprites->font2[timeleft % 10], NULL, screen, &rect);
/* draw the preview window */
rect.x = 0;
rect.y = 480 - 142;
rect.w = sprites->preview->w;
rect.h = sprites->preview->h;
SDL_BlitSurface(sprites->preview, NULL, screen, &rect);
/* draw the molecule's description - line 1 */
y = 0; /* compute the pixel length of the first line */
for (x = 0; x < (int)strlen(game->level_desc_line1); x++) {
y += font1_width[ascii2font1(game->level_desc_line1[x])];
}
/* rect.x = 72 - (strlen(game->level_desc_line1) * 5); */
rect.x = 72 - y;
rect.y = 362;
rect.h = 10;
rect.w = 10;
for (x = 0; x < 16; x++) {
SDL_BlitSurface(sprites->font1[ascii2font1(game->level_desc_line1[x])], NULL, screen, &rect);
/* rect.x += sprites->font1[0]->w; */
rect.x += (font1_width[ascii2font1(game->level_desc_line1[x])] << 1);
}
/* draw the molecule's description - line 2 */
y = 0; /* compute the pixel length of the second line */
for (x = 0; x < (int)strlen(game->level_desc_line2); x++) {
y += font1_width[ascii2font1(game->level_desc_line2[x])];
}
/* rect.x = 72 - (strlen(game->level_desc_line2) * 5); */
rect.x = 72 - y;
rect.y += sprites->font1[0]->h + 2;
for (x = 0; x < 16; x++) {
SDL_BlitSurface(sprites->font1[ascii2font1(game->level_desc_line2[x])], NULL, screen, &rect);
/* rect.x += sprites->font1[0]->w; */
rect.x += (font1_width[ascii2font1(game->level_desc_line2[x])] << 1);
}
/* Draw the solution preview inside the preview window */
for (y = 0; y < game->solution_height; y++) {
for (x = 0; x < game->solution_width; x++) {
if ((game->solution[x][y] & field_type) == field_atom) {
int preview_offset_x, preview_offset_y;
tile = sprites->satom[game->solution[x][y] & field_index];
preview_offset_x = (8 - game->solution_width) * (tile->w >> 1);
preview_offset_y = (7 - game->solution_height) * (tile->h >> 1);
if (game->level_desc_line2[0] == 0) { /* if the molecule's name uses two lines, adapt the offset of the preview */
preview_offset_y -= 8;
}
rect.x = 8 + preview_offset_x + (x * tile->w);
rect.y = 32 + (480 - 142) + preview_offset_y + (y * tile->h);
rect.w = tile->w;
rect.h = tile->h;
SDL_BlitSurface(tile, NULL, screen, &rect);
}
}
}
/* Refresh the screen */
SDL_Flip(screen);
}
static void flush_events() {
SDL_Event event;
while (SDL_PollEvent(&event) != 0);
}
static int wait_for_a_key(int timeout) {
SDL_Event event;
time_t timeouttime;
timeouttime = time(NULL) + timeout;
for (;;) {
SDL_Delay(20);
if (SDL_PollEvent(&event) != 0) {
if (event.type == SDL_QUIT) {
return(1);
} else if (event.type == SDL_KEYDOWN) {
if (event.key.keysym.sym == SDLK_ESCAPE) return(1);
return(0);
}
}
if (timeout > 0) {
if (time(NULL) >= timeouttime) return(0);
}
}
}
/* displays the level selection screen. returns the selected level to load, or -1 on QUIT request */
static int selectlevel(int curlevel, int max_auth_level, int last_level, SDL_Surface *infoscreen, SDL_Surface *levsel, SDL_Surface *levsel2, struct spritesstruct *sprites, SDL_Surface *screen, int *hiscores) {
SDL_Rect rect;
SDL_Event event;
SDL_Surface *tile;
int x, y;
struct atomixgame *game = atomix_initgame();
if (curlevel > max_auth_level) curlevel = max_auth_level;
for (;;) {
atomix_loadgame(game, curlevel, ATOMIX_SRC_MEM, hiscores);
SDL_BlitSurface(infoscreen, NULL, screen, NULL);
SDL_BlitSurface(levsel, NULL, screen, NULL);
if (max_auth_level > 1) SDL_BlitSurface(levsel2, NULL, screen, NULL);
/* Draw the solution preview inside the preview window */
for (y = 0; y < game->solution_height; y++) {
for (x = 0; x < game->solution_width; x++) {
if ((game->solution[x][y] & field_type) == field_atom) {
int preview_offset_y;
tile = sprites->satom[game->solution[x][y] & field_index];
preview_offset_y = (7 - game->solution_height) * (tile->h >> 1);
rect.x = (640 / 2) - (game->solution_width * (tile->w >> 1)) + (x * tile->w);
rect.y = 180 + preview_offset_y + (y * tile->h);
rect.w = tile->w;
rect.h = tile->h;
SDL_BlitSurface(tile, NULL, screen, &rect);
}
}
}
/* draw the level number */
/*rect.x = (640 / 2) - sprites->font2[0]->w;
rect.y = 220;
rect.w = sprites->font2[0]->w;
rect.h = sprites->font2[0]->h;
SDL_BlitSurface(sprites->font2[curlevel / 10], NULL, screen, &rect);
rect.x += sprites->font2[0]->w;
SDL_BlitSurface(sprites->font2[curlevel % 10], NULL, screen, &rect); */
/* draw 'completed' over the level number, if completed indeed */
if (curlevel < max_auth_level) {
rect.x = 10 + (640 / 2) - (sprites->completed->w / 2);
rect.y = 220;
SDL_BlitSurface(sprites->completed, NULL, screen, &rect);
}
/* refresh the screen */
SDL_Flip(screen);
flush_events();
/* Get keypress */
SDL_WaitEvent(&event);
if (event.type == SDL_QUIT) {
return(-1);
} else if (event.type == SDL_KEYDOWN) {
switch (event.key.keysym.sym) {
case SDLK_LEFT:
if (curlevel > 1) curlevel -= 1;
break;
case SDLK_RIGHT:
if ((curlevel < max_auth_level) && (curlevel < last_level)) curlevel += 1;
break;
case SDLK_RETURN:
return(curlevel);
break;
case SDLK_ESCAPE:
return(-1);
break;
default:
break;
}
}
}
}
static void getcfg(int *max_auth_level, int *hiscores, int last_level) {
FILE *fd;
int x, buff1, buff2;
fd = cfg_fopen("rb", "atomiks");
for (x = 0; x < last_level; x++) {
hiscores[x] = 0;
}
if (fd == NULL) {
*max_auth_level = 1;
} else {
*max_auth_level = fgetc(fd);
if (*max_auth_level < 1) *max_auth_level = 1;
for (x = 0; x < last_level; x++) {
buff1 = fgetc(fd);
buff2 = fgetc(fd);
if ((buff1 < 0) || (buff2 < 0)) break;
hiscores[x] = (buff1 << 8) | buff2;
}
fclose(fd);
}
}
static void savecfg(int max_auth_level, int *hiscores, int last_level) {
FILE *fd;
int x;
fd = cfg_fopen("wb", "atomiks");
if (fd == NULL) return;
fputc(max_auth_level, fd);
for (x = 0; x < last_level; x++) {
fputc((hiscores[x] >> 8) & 0xFF, fd);
fputc(hiscores[x] & 0xFF, fd);
}
fclose(fd);
}
#define last_level 30
int main(int argc, char **argv) {
SDL_Surface *screen = NULL;
struct spritesstruct sprites;
SDL_Surface *title, *infoscreen, *instructions, *intro[3], *levsel, *levsel2, *timeoutscreen, *pausedscreen, *creditscreen;
SDL_Event event;
struct atomixgame *game;
int x, exitflag = 0, gamejuststarted;
uint32_t nextscreenrefresh = 0;
int max_auth_level;
int hiscores[last_level];
Mix_Music *music_title, *music_end;
struct soundsstruct sounds;
Uint32 sdl_video_flags = SDL_SWSURFACE | SDL_DOUBLEBUF;
getcfg(&max_auth_level, hiscores, last_level);
sounds.soundflag = 1;
/* Look for command-line parameters (fullscreen only for now) */
for (x = 1; x < argc; x++) {
if (strcmp(argv[x], "--fullscreen") == 0) sdl_video_flags |= SDL_FULLSCREEN;
if (strcmp(argv[x], "--nosound") == 0) sounds.soundflag = 0;
}
/* Init SDL and set the video mode */
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
if ((screen = SDL_SetVideoMode(640, 480, 32, sdl_video_flags)) == NULL) {
puts("Error: unable to init screen!");
return(1);
}
/* init the audio system */
if ((Mix_Init(MIX_INIT_MOD) & MIX_INIT_MOD) == 0) printf("Could not Mix_Init() MOD!\n");
if (Mix_OpenAudio(44100, AUDIO_S16SYS, 1, 2048) != 0) printf("Mix_OpenAudio() error!\n");
Mix_AllocateChannels(4); /* allocate 4 mixing channels (we won't need that much anyway... 1 would be enough) */
/* Set window's title */
SDL_WM_SetCaption("Atomiks " PVER, NULL);
/* Hide the mouse cursor */
SDL_ShowCursor(SDL_DISABLE);
/* Set keyboard repeat rate */
SDL_EnableKeyRepeat(100, 70);
/* load music and sound effects */
loadMusic(&music_title, title_mod, title_mod_len);
if (music_title == NULL) puts("Ooops! loading title module failed :/");
loadMusic(&music_end, end_mod, end_mod_len);
if (music_end == NULL) puts("Ooops! loading end module failed :/");
loadSnd(&sounds.bzzz, bzzz_wav, bzzz_wav_len);
loadSnd(&sounds.explode, explode_wav, explode_wav_len);
loadSnd(&sounds.selected, selected_wav, selected_wav_len);
/* Preload all graphics */
loadGraphic(&title, title_png, title_png_len);
loadGraphic(&creditscreen, credits_png, credits_png_len);
loadGraphic(&timeoutscreen, timeout_png, timeout_png_len);
loadGraphic(&infoscreen, infoscreen_png, infoscreen_png_len);
loadGraphic(&pausedscreen, pausedscreen_png, pausedscreen_png_len);
loadGraphic(&instructions, instructs_png, instructs_png_len);
loadGraphic(&intro[0], intro1_png, intro1_png_len);
loadGraphic(&intro[1], intro2_png, intro2_png_len);
loadGraphic(&intro[2], intro3_png, intro3_png_len);
loadGraphic(&levsel, levsel_png, levsel_png_len);
loadGraphic(&levsel2, levsel2_png, levsel2_png_len);
loadGraphic(&sprites.completed, completed_png, completed_png_len);
/* load backgrounds */
loadSpriteSheet(&sprites.bg[0], 320, 240, 3, bg_png, bg_png_len);
/* load the preview window */
loadSpriteSheet(&sprites.preview, 71, 71, 1, preview_png, preview_png_len);
/* load the 'empty space' tile */
loadSpriteSheet(&sprites.empty, 16, 16, 1, empty_png, empty_png_len);
/* load atoms sprites */
loadSpriteSheet(&sprites.atom[0], 16, 16, 49, atoms_png, atoms_png_len);
/* load 'small' atoms sprites */
loadSpriteSheet(&sprites.satom[0], 8, 8, 49, satoms_png, satoms_png_len);
/* load the explosion sprites */
loadSpriteSheet(&sprites.explosion[0], 16, 16, 8, explosion_png, explosion_png_len);
/* load walls sprites */
loadSpriteSheet(&sprites.wall[0], 16, 16, 19, walls_png, walls_png_len);
/* load cursor sprites */
loadSpriteSheet(&sprites.cursor[0], 16, 16, 3, cursors_png, cursors_png_len);
/* load fonts */
loadSpriteSheet(&sprites.font1[0], 5, 5, 37, font1_png, font1_png_len);
loadSpriteSheet(&sprites.font2[0], 14, 16, 11, font2_png, font2_png_len);
loadSpriteSheet(&sprites.font3[0], 7, 8, 26, font3_png, font3_png_len);
/* start playing the module in an infinite loop */
if (sounds.soundflag != 0) {
if (Mix_PlayMusic(music_title, -1) != 0) printf("Mix_PlayMusic() error!\n");
}
/* Show the title screen for a little moment (fade in) */
for (x = 0; x < 256; x += 15) {
SDL_SetAlpha(title, SDL_SRCALPHA, x);
SDL_FillRect(screen, NULL, 0); /* fill the screen with black */
SDL_BlitSurface(title, NULL, screen, NULL);
SDL_Flip(screen);
SDL_Delay(30);
}
exitflag = wait_for_a_key(4);
flush_events();
for (x = 0; (x < 3) && (exitflag == 0); x++) {
SDL_BlitSurface(infoscreen, NULL, screen, NULL);
SDL_BlitSurface(intro[x], NULL, screen, NULL);
SDL_Flip(screen);
SDL_Delay(100);
exitflag = wait_for_a_key(0);
}
flush_events();
/* Init the game and start on level 1 */
game = atomix_initgame();
if (exitflag == 0) {
if ((game->level = selectlevel(1, max_auth_level, last_level, infoscreen, levsel, levsel2, &sprites, screen, hiscores)) < 1) {
exitflag = 1;
game->level = 1;
}
atomix_loadgame(game, game->level, ATOMIX_SRC_MEM, hiscores);
}
Mix_FadeOutMusic(2000); /* slowly stop playing the background music */
gamejuststarted = 1;
nextscreenrefresh = 0; /* force a first refresh */
while (exitflag == 0) {
/* Wait for next event, while keeping the screen refreshed */
while (exitflag == 0) {
/* keep redrawing the screen every 0.2s */
if (SDL_GetTicks() > nextscreenrefresh) {
nextscreenrefresh = SDL_GetTicks() + 200;
draw_game_screen(game, &sprites, screen, 0, time(NULL)); /* draw the game only if we have time */
/* if we are starting Atomix experience, display a short notice */
if ((game->level == 1) && (max_auth_level == 1) && (gamejuststarted == 1)) {
SDL_BlitSurface(instructions, NULL, screen, NULL);
SDL_Flip(screen);
SDL_Delay(1000);
gamejuststarted = 0;
flush_events();
exitflag = wait_for_a_key(0);
game->time_end = time(NULL) + game->duration;
}
if (game->time_end < time(NULL)) {
SDL_BlitSurface(timeoutscreen, NULL, screen, NULL);
SDL_Flip(screen);
SDL_Delay(1000);
flush_events();
exitflag = wait_for_a_key(0);
atomix_loadgame(game, game->level, ATOMIX_SRC_MEM, hiscores);
}
}
if (SDL_PollEvent(&event) != 0) break;
SDL_Delay(20);
}
if (event.type == SDL_QUIT) {
exitflag = 1;
} else if ((event.type == SDL_ACTIVEEVENT) && (event.active.state & SDL_APPINPUTFOCUS)) { /* might be an indicator of lost/gained focus */
if (event.active.gain == 0) { /* Lost focus -> pausing the game until focus regained! */
time_t pausedtime = time(NULL);
SDL_BlitSurface(pausedscreen, NULL, screen, NULL); /* draw paused screen */
for (;;) {
SDL_Flip(screen);
SDL_WaitEvent(&event);
if (event.type == SDL_QUIT) {
exitflag = 1;
break;
} else if (event.type == SDL_ACTIVEEVENT) {
if ((event.active.gain != 0) && (event.active.state & SDL_APPINPUTFOCUS)) {
game->time_end += (time(NULL) - pausedtime);
break;
}
}
}
}
} else if (event.type == SDL_KEYDOWN) {
int cursorx_backup = game->cursorx;
int cursory_backup = game->cursory;
int tmp;
switch (event.key.keysym.sym) {
case SDLK_LEFT:
if (game->cursorstate == 0) {
move_cursor_left(game, screen, &sprites);
} else {
tmp = atomix_getmovedistance(game, 3);
if (tmp != 0) {
if (game->score >= 5) game->score -= 5;
game->cursorx -= tmp;
move_tile(game, cursorx_backup, cursory_backup, game->cursorx, game->cursory, screen, sprites.empty, sprites.atom[game->field[cursorx_backup][cursory_backup] & field_index], sprites.cursor[game->cursorstate], &sounds);
}
}
break;
case SDLK_RIGHT:
if (game->cursorstate == 0) {
move_cursor_right(game, screen, &sprites);
} else {
tmp = atomix_getmovedistance(game, 1);
if (tmp != 0) {
if (game->score >= 5) game->score -= 5;
game->cursorx += tmp;
move_tile(game, cursorx_backup, cursory_backup, game->cursorx, game->cursory, screen, sprites.empty, sprites.atom[game->field[cursorx_backup][cursory_backup] & field_index], sprites.cursor[game->cursorstate], &sounds);
}
}
break;
case SDLK_UP:
if (game->cursorstate == 0) {
move_cursor_up(game, screen, &sprites);
} else {
tmp = atomix_getmovedistance(game, 0);
if (tmp != 0) {
if (game->score >= 5) game->score -= 5;
game->cursory -= tmp;
move_tile(game, cursorx_backup, cursory_backup, game->cursorx, game->cursory, screen, sprites.empty, sprites.atom[game->field[cursorx_backup][cursory_backup] & field_index], sprites.cursor[game->cursorstate], &sounds);
}
}
break;
case SDLK_DOWN:
if (game->cursorstate == 0) {
move_cursor_down(game, screen, &sprites);
} else {
tmp = atomix_getmovedistance(game, 2);
if (tmp != 0) {
if (game->score >= 5) game->score -= 5;
game->cursory += tmp;
move_tile(game, cursorx_backup, cursory_backup, game->cursorx, game->cursory, screen, sprites.empty, sprites.atom[game->field[cursorx_backup][cursory_backup] & field_index], sprites.cursor[game->cursorstate], &sounds);
}
}
break;
case SDLK_ESCAPE:
if (sounds.soundflag != 0) {
if (Mix_PlayMusic(music_title, -1) != 0) printf("Mix_PlayMusic() error!\n");
}
if ((game->level = selectlevel(game->level, max_auth_level, last_level, infoscreen, levsel, levsel2, &sprites, screen, hiscores)) < 0) {
exitflag = 1;
} else {
Mix_FadeOutMusic(2000);
atomix_loadgame(game, game->level, ATOMIX_SRC_MEM, hiscores);
}
break;
case SDLK_RETURN:
if ((game->field[game->cursorx][game->cursory] & field_type) == field_atom) {
if (game->cursorstate == 0) {
game->cursorstate = game->cursortype;
if (sounds.soundflag != 0) Mix_PlayChannel(-1, sounds.selected, 0);
} else {
game->cursorstate = 0;
}
}
break;
default:
break;
}
}
/* After every move, check if we are in a winning position */
if (atomix_checksolution(game) != 0) {
time_t tmptime;
if (game->level == max_auth_level) max_auth_level += 1;
draw_game_screen(game, &sprites, screen, 1, time(NULL));
draw_anim_explosions(game, &sprites, screen, &sounds); /* animate atoms explosion */
SDL_Delay(750);
for (tmptime = time(NULL); tmptime <= game->time_end; tmptime++) {
game->score += 10;
draw_game_screen(game, &sprites, screen, 1, tmptime);
if (tmptime % 2) SDL_Delay(10);
}
if (game->score > hiscores[game->level - 1]) hiscores[game->level - 1] = game->score;
SDL_Delay(1000);
if (game->level >= last_level) { /* catch final level for congrats screen! */
SDL_Rect rectcredits, rectscreen;
int firstloop = 1;
if (sounds.soundflag != 0) {
if (Mix_FadeInMusic(music_end, -1, 200) != 0) printf("Mix_FadeInMusic() error!\n");
}
rectscreen.x = 320 - (creditscreen->w >> 1);
rectscreen.y = 72;
rectscreen.w = creditscreen->w;
rectscreen.h = creditscreen->h;
rectcredits.x = 0;
rectcredits.y = 0;
rectcredits.w = creditscreen->w;
rectcredits.h = 362;
for (;;) {
SDL_BlitSurface(infoscreen, NULL, screen, NULL);
SDL_BlitSurface(creditscreen, &rectcredits, screen, &rectscreen);
SDL_Flip(screen);
if (firstloop != 0) {
SDL_Delay(5000);
firstloop = 0;
}
SDL_Delay(100);
if (SDL_PollEvent(&event) > 0) {
if (event.type == SDL_QUIT) {
exitflag = 1;
break;
} else if (event.type == SDL_KEYDOWN) {
break;
}
}
if (rectcredits.y + rectcredits.h < creditscreen->h) rectcredits.y += 1;
}
Mix_FadeOutMusic(2000);
flush_events();
game->level -= 1;
SDL_Delay(2000);
}
if (exitflag == 0) {
if (sounds.soundflag != 0) {
if (Mix_PlayMusic(music_title, -1) != 0) printf("Mix_PlayMusic() error!\n");
}
flush_events();
if ((game->level = selectlevel(game->level + 1, max_auth_level, last_level, infoscreen, levsel, levsel2, &sprites, screen, hiscores)) < 0) {
exitflag = 1;
game->level = 1;
}
Mix_FadeOutMusic(2000);
atomix_loadgame(game, game->level, ATOMIX_SRC_MEM, hiscores);
}
}
}
flush_events();
/* if some music is playing, fade it out */
if (Mix_PlayingMusic() != 0) Mix_FadeOutMusic(500);
/* fade out the screen by applying a black surface with decreasing transparency */
SDL_FillRect(sprites.bg[0], NULL, 0x00000033L);
for (x = 0; x < 16; x++) {
SDL_BlitSurface(sprites.bg[0], NULL, screen, NULL);
SDL_Flip(screen);
SDL_Delay(30);
}
savecfg(max_auth_level, hiscores, last_level);
/* cleaning up stuff */
free(game);
/* SDL_FreeSurface(sprites.bg[0]);
SDL_FreeSurface(creditscreen);
SDL_FreeSurface(title);
for (x = 0; x < 18; x++) SDL_FreeSurface(sprites.atom[x]);
for (x = 0; x < 8; x++) SDL_FreeSurface(sprites.wall[x]);
for (x = 0; x < 3; x++) SDL_FreeSurface(intro[x]); */
Mix_FreeMusic(music_title);
Mix_FreeChunk(sounds.bzzz);
Mix_FreeChunk(sounds.explode);
Mix_FreeChunk(sounds.selected);
Mix_CloseAudio(); /* this one takes a long time (~2s) when using the PulseAudio driver... This is a known bug, there's not much I can do about this */
SDL_Quit();
return(0);
}