/*
 *  Overreact - Mr. 4th Dimention
 *      Allen Webster
 *  03.09.2015 (mm.dd.yyyy)
 *
 * Game Layer.
 */

// TOP

internal Memory_Block
make_memory_block(void *base, i32 size){
	Memory_Block block;
	block.base = base;
	block.size = size;
	block.cursor = 0;
	return block;
}

internal void*
get_memory_size(Memory_Block *block, i32 size){
	Assert(block->cursor + size <= block->size);
	void *result = (u8*)block->base + block->cursor;
	block->cursor += size;
	return result;
}

#define get_mem(t,block) (t*)get_memory_size(block, sizeof(t))
#define get_mem_size(block,size) get_memory_size(block, size)
#define get_mem_array(t,block,size) (t*)get_memory_size(block, sizeof(t)*(size))
#define remaining_mem_i32(block) (i32)((block).size - (block).cursor)

#define get_i(x,y) ((x)+(y)*WIDTH)

struct Temp_Memory{
    Memory_Block *block;
    i32 prev_cursor;
};

internal Temp_Memory
begin_temp_memory(Memory_Block *block){
    Temp_Memory result;
    result.block = block;
    result.prev_cursor = block->cursor;
    return result;
}

internal void
end_temp_memory(Temp_Memory temp){
    temp.block->cursor = temp.prev_cursor;
}

struct Wave_Pair{
    i32 left, right;
};

internal bool32
mix_track(Track *track, Wave_Pair *value){
    bool32 result = 0;
    if (track->playing && track->frames_delay == 0){
        Sound sound = track->sound;
        
        i16 *src_prev = sound.samples + track->sample_pos*2;
        i16 *src_next;
        if (track->sample_pos + 1 == sound.sample_count){
            src_next = sound.samples;
        }
        else{
            src_next = src_prev + 2;
        }
        
        real32 dec = track->sample_pos_dec;
        real32 inv_dec = 1.f - dec;
        
        dec *= track->volume;
        inv_dec *= track->volume;
        
        value->left += (i32)((*src_prev)*inv_dec + (*src_next)*dec);
        
        ++src_prev;
        ++src_next;
        
        value->right += (i16)((*src_prev)*inv_dec + (*src_next)*dec);
        
        track->sample_pos_dec += sound.scan_speed*track->bend;
        if (track->sample_pos_dec >= 1.f){
            track->sample_pos += (i32)(track->sample_pos_dec);
            track->sample_pos_dec = DecPart(track->sample_pos_dec);
            
            if (track->sample_pos >= sound.sample_count){
                if (track->looping){
                    track->sample_pos -= sound.sample_count;
                }
                else{
                    track->playing = 0;
                    result = 1;
                }
            }
        }
    }
    
    return result;
}

internal
SIG_APP_FILL_SAMPLES(game_fill_samples){
    App_Vars *vars = (App_Vars*)memory.data;
    
    if (vars->silence_timer == 0){

#if 0
        i16 *dst = target.samples;
        
        for (i32 i = 0; i < target.count_per_channel; ++i){
            real32 wave_hz = 440.f;
            real32 t = vars->wave_t * TAU32;
            real32 v = sinf(t);
            
            i16 value = (i16)(max_i16 * v);
            *dst++ = value;
            *dst++ = value;
            
            vars->wave_t += wave_hz / target.samples_per_second;
        }
        
        vars->wave_t = DecPart(vars->wave_t);
#endif
        
        i16 *dst = target.samples;
        for (i32 i = 0; i < target.count_per_channel; ++i){
            Wave_Pair value = {};
            
            for (i32 j = 0; j < ArrayCount(vars->sfx_tracks); ++j){
                Track *track = vars->sfx_tracks + j;
                if (mix_track(track, &value)){
                    track->next_free = vars->sfx_free;
                    vars->sfx_free = track;
                }
            }
            
            if (vars->music.playing){
                if (mix_track(&vars->music, &value)){
                    vars->song_done = 1;
                }
            }
            
            if (value.left > max_i16){
                value.left = max_i16;
            }
            else if (value.left < min_i16){
                value.left = min_i16;
            }
            
            if (value.right > max_i16){
                value.right = max_i16;
            }
            else if (value.right < min_i16){
                value.right = min_i16;
            }
            
            *dst++ = (i16)value.left;
            *dst++ = (i16)value.right;
        }
    }
    
    for (i32 j = 0; j < ArrayCount(vars->sfx_tracks); ++j){
        Track *track = vars->sfx_tracks + j;
        if (track->playing && track->frames_delay > 0){
            --track->frames_delay;
        }
    }
}

#define A_SHIFT 24
#define R_SHIFT 16
#define G_SHIFT 8
#define B_SHIFT 0

#define A_MASK 0xFF000000
#define R_MASK 0x00FF0000
#define G_MASK 0x0000FF00
#define B_MASK 0x000000FF

internal u32
compress_color(Vec3 color){
	return
		((u8)(255*color.r) << R_SHIFT) +
		((u8)(255*color.g) << G_SHIFT) +
		((u8)(255*color.b) << B_SHIFT);
}

internal u32
compress_color(Vec4 color){
	return
		((u8)(255*color.a) << A_SHIFT) +
		((u8)(255*color.r) << R_SHIFT) +
		((u8)(255*color.g) << G_SHIFT) +
		((u8)(255*color.b) << B_SHIFT);
}

internal Vec3
decompress_color(u32 color){
	return V3(((color & R_MASK) >> R_SHIFT) / 255.f,
			  ((color & G_MASK) >> G_SHIFT) / 255.f,
			  ((color & B_MASK) >> B_SHIFT)  / 255.f);
}

internal Vec4
decompress_color_alpha(u32 color){
	return V4(((color & R_MASK) >> R_SHIFT)  / 255.f,
			  ((color & G_MASK) >> G_SHIFT)  / 255.f,
			  ((color & B_MASK) >> B_SHIFT) / 255.f,
			  ((color & A_MASK) >> A_SHIFT) / 255.f);
}

internal void
draw_clear(Vec4 color){
	glClearColor(color.r, color.g, color.b, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

internal Render_Texture
make_render_texture(Image image){
	GLuint tex;
	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height,
				 0, GL_RGBA, GL_UNSIGNED_BYTE, image.data);
    
	Render_Texture texture;
	texture.texid = tex;
	texture.width = image.width;
	texture.height = image.height;
    texture.img_width = image.img_width;
    texture.img_height = image.img_height;
    texture.tex_x = (real32)texture.img_width / texture.width;
    texture.tex_y = (real32)texture.img_height / texture.height;
    
	return texture;
}

internal void
draw_texture(Render_Texture texture, Vec2 center, Vec2 halfdim,
             real32 rotation = 0.f, Vec4 blend = {1.f, 1.f, 1.f, 1.f}){
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    
    glTranslatef(center.x, center.y, 0.f);
    glRotatef(rotation, 0, 0, 1);
    
    glColor4f(blend.r, blend.g, blend.b, blend.a);
    glBindTexture(GL_TEXTURE_2D, texture.texid);
    glBegin(GL_QUADS);
    {
        glTexCoord2f(0.f, texture.tex_y);
        glVertex3f(-halfdim.x, halfdim.y, 0.f);
        
        glTexCoord2f(texture.tex_x, texture.tex_y);
        glVertex3f(halfdim.x, halfdim.y, 0.f);
        
        glTexCoord2f(texture.tex_x, 0.f);
        glVertex3f(halfdim.x, -halfdim.y, 0.f);
        
        glTexCoord2f(0.f, 0.f);
        glVertex3f(-halfdim.x, -halfdim.y, 0.f);
    }
    glEnd();
    
    glPopMatrix();
}

inline internal void
draw_texture(Render_Texture texture, Vec2 center,
             real32 rotation = 0.f, Vec4 blend = {1.f, 1.f, 1.f, 1.f}){
    Vec2 halfdim;
    halfdim.x = texture.img_width * 0.5f;
    halfdim.y = texture.img_height * 0.5f;
    draw_texture(texture, center, halfdim, rotation, blend);
}

internal void
draw_rectangle(real32 x1, real32 y1,
			   real32 x2, real32 y2,
			   Vec4 color, real32 z = 0.f){
    
	glBindTexture(GL_TEXTURE_2D, 0);
	glColor4f(color.r, color.g, color.b, color.a);
	glBegin(GL_POLYGON);
	{
		glVertex3f(x1, y2, z);
		glVertex3f(x2, y2, z);
		glVertex3f(x2, y1, z);
		glVertex3f(x1, y1, z);
	}
	glEnd();
}

internal void
draw_text(Game_Render_Target *target, Font *font, real32 *x, real32 *y, char *str, i32 len, Vec4 color,
          real32 start_x = 0.f, real32 x_limit = 10000.f){
    u32 packed_color = compress_color(color);
    for (i32 i = 0; i < len; ++i){
        char c = str[i];
        font_draw_glyph(target, font, c, *x, *y, packed_color);
        *x += font->glyphs[c].advance;
        if (*x > x_limit){
            *x = start_x;
            *y += font->height;
        }
    }
}

internal bool32
load_texture(char *filename, Render_Texture *texture, Memory_Block *block){
    bool32 result = 0;
    Image image;
    Bitmap_File file = {};
    bitmap_open_file(filename, &file);
    if (file.file.data){
        Temp_Memory temp = begin_temp_memory(block);
        
        i32 size = bitmap_data_requirement(&file);
        image.data = (u32*)get_mem_size(block, size);
        bitmap_fill_image(&file, &image);
        bitmap_free_file(&file);
        
        *texture = make_render_texture(image);
        
        end_temp_memory(temp);
        result = 1;
    }
    
    return result;
}

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

internal bool32
load_texture_png(char *filename, Render_Texture *texture, Memory_Block *block){
    Image image;
    i32 n;
    image.data = (u32*)stbi_load(filename, &image.width, &image.height, &n, 0);
    TentativeAssert(image.data);
    
    if (image.data){
        Assert(n == 4);
        Image POT_image;
        POT_image.width = round_up_POT(image.width);
        POT_image.height = round_up_POT(image.height);
        POT_image.img_width = image.width;
        POT_image.img_height = image.height;
        
        Temp_Memory temp = begin_temp_memory(block);
        
        POT_image.data = get_mem_array(u32, block, POT_image.width*POT_image.height);
        
        u32 *src_line = image.data;
        u32 *dst_line = POT_image.data;
        for (i32 y = 0; y < image.height; ++y){
            u32 *src = src_line;
            u32 *dst = dst_line;
            for (i32 x = 0; x < image.width; ++x){
                *dst++ = *src++;
            }
            src_line += image.width;
            dst_line += POT_image.width;
        }
        
        *texture = make_render_texture(POT_image);
        
        end_temp_memory(temp);
        stbi_image_free(image.data);
        
        return 1;
    }
    
    return 0;
}

internal bool32
load_sound(char *filename, Sound *sound, i32 target_samples_per_second, Memory_Block *block){
    bool32 result = 0;
    Wav_File file;
    wav_open_file(filename, &file);
    if (file.file.data){
        i32 size = wav_data_requirement(&file);
        sound->samples = (i16*)get_mem_size(block, size);
        wav_fill_sound(&file, sound, target_samples_per_second);
        result = 1;
    }
    return result;
}

internal bool32
load_font(char *filename, Font *font, i32 pt_size, Memory_Block *block){
    bool32 result = 0;
    Temp_Memory temp = begin_temp_memory(block);
    i32 size = remaining_mem_i32(*block);
    void *mem = get_mem_size(block, size);
    if (font_load(filename, font, pt_size, mem, size)){
        result = 0;
    }
    end_temp_memory(temp);
    return result;
}

internal bool32
can_move_to(App_Vars *vars, Entity *entity, i32 x, i32 y, bool32 *out_of_grid){
    AllowLocal(entity);
    bool32 result = 1;
    if (y >= HEIGHT || y < 0 ||
        x >= WIDTH || x < 0){
        result = 0;
        *out_of_grid = 1;
    }
    else{
        Entity *other_entity = vars->grid[get_i(x, y)];
        if (other_entity){
            result = 0;
        }
        *out_of_grid = 0;
    }
    return result;
}

inline internal bool32
can_move_to(App_Vars *vars, Entity *entity, i32 x, i32 y){
    bool32 throw_away;
    return can_move_to(vars, entity, x, y, &throw_away);
}

internal Entity*
spawn_entity(App_Vars *vars){
    Assert(vars->entity_count < vars->entity_max);
    
    Entity *result = vars->entities + vars->entity_count++;
    *result = {};
    
    return result;
}

internal void
make_random_block(App_Vars *vars){
    Assert(vars->spawn_count < vars->spawn_max);
    
    Spawn_Request *spawn = vars->spawns + vars->spawn_count++;
    spawn->random = 1;
}

internal void
make_zombie(App_Vars *vars, i32 x, i32 y){
    Assert(vars->spawn_count < vars->spawn_max);
    
    Spawn_Request *spawn = vars->spawns + vars->spawn_count++;
    spawn->random = 0;
    spawn->x = x;
    spawn->y = y;
    spawn->type = ZOMBIE;
}

internal Entity*
make_brain(App_Vars *vars, i32 x, i32 y){
    Assert(vars->spawn_count < vars->spawn_max);
    
    Spawn_Request *spawn = vars->spawns + vars->spawn_count++;
    spawn->random = 0;
    spawn->x = x;
    spawn->y = y;
    spawn->type = BRAIN;
}

internal void
move_entity(App_Vars *vars, Entity *entity, i32 x, i32 y){
    i32 old_i = get_i(entity->grid_x, entity->grid_y);
    i32 new_i = get_i(x, y);
    
    Assert(vars->grid[old_i] == entity);
    Assert(vars->grid[new_i] == 0);
    
    vars->grid[old_i] = 0;
    vars->grid[new_i] = entity;
    
    entity->grid_x = x;
    entity->grid_y = y;
}

internal Entity*
get_grid_entity(App_Vars *vars, i32 x, i32 y, bool32 *out_of_bounds){
    if (x < 0 || y < 0 || x >= WIDTH || y >= HEIGHT){
        *out_of_bounds = 1;
        return 0;
    }
    else{
        *out_of_bounds = 0;
        return vars->grid[get_i(x, y)];
    }
}

inline internal Entity*
get_grid_entity(App_Vars *vars, i32 x, i32 y){
    bool32 throw_away;
    return get_grid_entity(vars, x, y, &throw_away);
}

internal i32
points_for_destruction(Block_Type type){
    i32 result = 0;
    
    switch (type){
    case ZOMBIE:
        result = 15;
        break;
        
    case HUMAN:
        result = 1;
        break;
        
    case BRAIN:
        result = 1;
        break;
        
    case AMMO:
        result = 1;
        break;
        
    case BOMB:
        result = 5;
        break;
        
    case WALL:
        result = 100;
        break;
    }

    return result;
}

internal bool32
play_sound_effect(App_Vars *vars, Sound sound, real32 volume, real32 bend,
                  i32 delay = 0){
    bool32 result = 0;
    
    if (vars->sfx_free){
        result = 1;
        Track *track = vars->sfx_free;
        vars->sfx_free = track->next_free;
        *track = {};
        
        track->sound = sound;
        track->playing = 1;
        track->volume = volume;
        track->bend = bend;
        track->frames_delay = delay;
    }
    
    return result;
}

internal real32
random_real32(App_Vars *vars, real32 min, real32 max){
    real32 result;
    u32 x = pcg32_random_r(&vars->rnd) % 1000000;
    result = min + (x / 1000000.f)*(max - min);
    return result;
}

globalvar real32 BEND_UP = 1.2f;
globalvar real32 BEND_DOWN = 0.8f;

internal void
mark_block_destroyed(App_Vars *vars, Entity *entity){
    if (!entity->death_marked){
        Assert(vars->free_count < vars->free_max);
        vars->to_free[vars->free_count++] = entity;
        vars->need_to_fill_gaps = 1;
        vars->fill_timer = COMBO_TIME;
        entity->death_marked = 1;
        
        vars->score += points_for_destruction(entity->type);
        vars->nonsense_score = pcg32_random_r(&vars->rnd) % 1000;
    }
}

inline internal i32
get_entity_index(App_Vars *vars, Entity *entity){
    Assert(entity >= vars->entities);
    return (i32)(entity - vars->entities);
}

inline internal Grid_Pos
get_grid_pos(Entity *entity){
    Grid_Pos result;
    result.x = entity->grid_x;
    result.y = entity->grid_y;
    return result;
}

struct Entity_Y_Data{
    i32 index, y;
};

internal i32
quick_partition(Entity_Y_Data *data, i32 start, i32 pivot){
    i32 mid = (start + pivot)/2;
    Swap(Entity_Y_Data, data[mid], data[pivot]);
    i32 pivot_y = data[pivot].y;
    for (i32 i = start; i < pivot; ++i){
        if (data[i].y < pivot_y){
            Swap(Entity_Y_Data, data[i], data[start]);
            ++start;
        }
    }
    Swap(Entity_Y_Data, data[pivot], data[start]);
    return start;
}

internal void
quick_sort(Entity_Y_Data *data, i32 start, i32 pivot){
    if (start < pivot){
        i32 mid = quick_partition(data, start, pivot);
        quick_sort(data, start, mid-1);
        quick_sort(data, mid+1, pivot);
    }
}

internal i32
quick_partition(void **data, i32 start, i32 pivot){
    i32 mid = (start + pivot)/2;
    Swap(void*, data[mid], data[pivot]);
    void *pivot_ptr = data[pivot];
    for (i32 i = start; i < pivot; ++i){
        if (data[i] > pivot_ptr){
            Swap(void*, data[i], data[start]);
            ++start;
        }
    }
    Swap(void*, data[pivot], data[start]);
    return start;
}

internal void
quick_sort(void **data, i32 start, i32 pivot){
    if (start < pivot){
        i32 mid = quick_partition(data, start, pivot);
        quick_sort(data, start, mid-1);
        quick_sort(data, mid+1, pivot);
    }
}

internal i32
int_to_string(char *buffer, i32 x, i32 force_len = 0){
    Assert(x >= 0);
    i32 i = 16;
    while (x != 0){
        char c = (char)(x % 10);
        x /= 10;
        c += '0';
        buffer[--i] = c;
    }
    i32 l = 16 - i;
    while (l < force_len){
        ++l;
        buffer[--i] = '0';
    }
    return i;
}

internal bool32
do_text_field(i32 my_index, i32 active_index, bool32 blink_on,
              Game_Render_Target *render_target, Game_Input input,
              Font *font, real32 text_x, real32 text_y,
              i32 *len, char *buffer, i32 max){
    
    bool32 active = (my_index == active_index);
    
    if (active){
        if (input.key_input){
            if (input.key_code == 0){
                if (*len > 0){
                    *len -= 1;
                }
            }
            else if (input.key_code != 1){
                if (*len < max){
                    buffer[*len] = input.key_code;
                    *len += 1;
                }
            }
        }
    }
    
    Vec4 color = V4(0.9f, 0.9f, 0.9f, 1.f);
    if (active){
        color = V4(1.f, 1.f, 1.f, 1.f);
    }
    
    draw_text(render_target, font, &text_x, &text_y, buffer, *len, color);
    
    if (active && blink_on){
        char cursor = '|';
        draw_text(render_target, font, &text_x, &text_y, &cursor, 1, color);
    }
    
    return (active && input.key_input && input.key_code == 1);
}

internal bool32
do_button(i32 my_index, i32 active_index,
          Game_Input input, Render_Texture *textures,
          real32 x, real32 y){
    
    bool32 active = (my_index == active_index);
    
    draw_texture(textures[active], V2(x, y));
    
    return (active && input.key_input && input.key_code == 1);
}

#define START_WITH_GAME_OVER 0
#define SKIP_TITLE_SCREEN 0

inline internal i32
get_level_fall_tick_time(App_Vars *vars, i32 level){
    i32 real_level = level;
    if (real_level >= ArrayCount(levels)){
        real_level = ArrayCount(levels) - 1;
    }
    return (levels[real_level].fall_tick_time);
}

inline internal i32
get_level_min_score(App_Vars *vars, i32 level){
    i32 result;
    i32 real_level = level;
    if (real_level >= ArrayCount(levels)){
        result = levels[ArrayCount(levels)-1].fall_tick_time;
        result = (level - ArrayCount(levels) + 1)*1200;
    }
    else{
        result = (levels[real_level].min_score);
    }
    return result;
}

internal void
game_set_to_new(App_Vars *vars){
    vars->level = 0;
    vars->fall_timer = get_level_fall_tick_time(vars, vars->level);
    
    vars->spawn_count = 0;
    vars->spawn_max = ArrayCount(vars->spawns);
    
    vars->free_count = 0;
    vars->free_max = ArrayCount(vars->to_free);
    
    vars->entity_count = 0;
    vars->entity_max = ArrayCount(vars->entities);
    vars->need_new_block = 1;
    
    vars->particle_count = 0;
    vars->particle_max = ArrayCount(vars->particles);
    
    vars->refall_timer = 0;
    vars->game_over = 0;
    vars->need_to_fill_gaps = 0;
    vars->chain_reacting = 0;
    vars->reaction_timer = 0;
    vars->fill_timer = 0;
    vars->score = 0;
    vars->nonsense_score = 0;
    vars->active_field = 0;
    vars->blink_timer = 0;
    vars->user_name_len = 0;
    vars->user_token_len = 0;
    
    memset(vars->grid, 0, sizeof(Entity*)*WIDTH*HEIGHT);

    vars->looked_up_teaser = 0;
    vars->teaser_name_len = 0;
    vars->teaser_score_len = 0;
    
    vars->controls_screen = 0;
    vars->title_screen = 1;
    
    vars->music = {};
    vars->music.sound = vars->menu_music;
    vars->music.playing = 1;
    vars->music.looping = 1;
    vars->music.volume = 0.3f;
    vars->music.bend = 1.f;
    
    for (i32 i = ArrayCount(vars->sfx_tracks) - 2; i >= 0; --i){
        vars->sfx_tracks[i].next_free = vars->sfx_tracks + i + 1;
    }
    
    vars->sfx_free = vars->sfx_tracks;
}

persist real32 GRID_STRIDE = 94.0f;

internal Vec2
get_screen_pos(Entity *entity){
    persist Vec2 top_left = {265.f, 70.f - GRID_STRIDE};
    
    Vec2 result;
    result.x = entity->show_x*GRID_STRIDE;
    result.y = entity->show_y*GRID_STRIDE;
    result += top_left;
    
    return result;
}

// THING GETS BROKEN
internal void
spawn_particles_type_1(App_Vars *vars, i32 prt_index, Vec2 pos, i32 count){
    Particle_Type prt_type = vars->prt_types[prt_index];
    Particle *particles = vars->particles;
    i32 particle_count = vars->particle_count;
    
    if (vars->particle_max < particle_count + count){
        count = vars->particle_max - particle_count;
    }
    
    for (i32 r = count; r > 0; --r){
        Particle part;
        part.tex_index = (pcg32_random_r(&vars->rnd) % prt_type.tex_count) + prt_type.first_tex_id;
        part.pos = pos;
        part.vel.x = random_real32(vars, -20.f, 20.f);
        part.vel.y = random_real32(vars, -20.f, 0.f);
        part.life_counter = max_i32;
        part.rot = random_real32(vars, 0.f, 360.f);
        part.rot_vel = random_real32(vars, -10.f, 10.f);
        
        Assert(particle_count < vars->particle_max);
        particles[particle_count++] = part;
    }
    
    vars->particle_count = particle_count;
}

// AMMO BOX GETS USED
internal void
spawn_particles_type_2(App_Vars *vars, i32 prt_index, Vec2 pos, i32 count){
    Particle_Type prt_type = vars->prt_types[prt_index];
    Particle *particles = vars->particles;
    i32 particle_count = vars->particle_count;
    
    if (vars->particle_max < particle_count + count){
        count = vars->particle_max - particle_count;
    }
    
    for (i32 r = count; r > 0; --r){
        Particle part;
        part.tex_index = (pcg32_random_r(&vars->rnd) % prt_type.tex_count) + prt_type.first_tex_id;
        part.pos = pos;
        part.vel.x = random_real32(vars, -10.f, 10.f);
        part.vel.y = random_real32(vars, 0.f, 0.f);
        part.life_counter = max_i32;
        part.rot = random_real32(vars, 0.f, 360.f);
        part.rot_vel = random_real32(vars, -10.f, 10.f);
        
        Assert(particle_count < vars->particle_max);
        particles[particle_count++] = part;
    }
    
    vars->particle_count = particle_count;
}

// STATIC PARTICLE
internal void
spawn_particles_type_3(App_Vars *vars, i32 prt_index, Vec2 pos, real32 rot){
    Particle_Type prt_type = vars->prt_types[prt_index];
    Particle *particles = vars->particles;
    i32 particle_count = vars->particle_count;
    
    if (particle_count < vars->particle_max){
        Particle part;
        part.tex_index = (pcg32_random_r(&vars->rnd) % prt_type.tex_count) + prt_type.first_tex_id;
        part.pos = pos;
        part.vel = {};
        part.life_counter = 5;
        part.rot = rot;
        part.rot_vel = 0;
        
        particles[particle_count++] = part;
        
        vars->particle_count = particle_count;
    }
}

internal
SIG_APP_STEP(game_step){
    App_Vars *vars = (App_Vars*)memory.data;
    Assert(sizeof(App_Vars) < memory.size);
    
    if (first){
        font_init();
        *vars = {};
        vars->block = make_memory_block((u8*)memory.data + sizeof(App_Vars),
                                        memory.size - sizeof(App_Vars));
        
        // TODO(allen): thread the gamejolt api?
        vars->gj = gj_init(80985, "031106786fa9dc6103062c42ee6f04ea");
        vars->table_id = 84092;
        
#if 0
        gj_login(vars->gj, "OVERREACT", "F3DE5E");
        gj_post_score(vars->gj, vars->table_id, "ZERO", 1, "", "");
#endif
        
        {
            Temp_Memory temp = begin_temp_memory(&vars->block);
            
            GJ_Score_Data *scores = get_mem_array(GJ_Score_Data, &vars->block, 5);
            i32 score_count = gj_get_scores(vars->gj, vars->table_id, 5, scores);
            
            for (i32 i = 0; i < score_count; ++i){
                gj_free_score(scores[i]);
            }
            
            end_temp_memory(temp);
        }
        
        vars->silence_timer = 70;
        
        load_texture_png("BRAINZ_background.png", &vars->background, &vars->block);
        
        load_texture_png("BRAINZ_human1.png", &vars->human[0][0], &vars->block);
        load_texture_png("BRAINZ_humanzombie1_1.png", &vars->human[0][1], &vars->block);
        load_texture_png("BRAINZ_humanzombie1_2.png", &vars->human[0][2], &vars->block);
        load_texture_png("BRAINZ_humanzombie1_3.png", &vars->human[0][3], &vars->block);
        load_texture_png("BRAINZ_zombie1.png", &vars->zombie[0], &vars->block);
        load_texture_png("BRAINZ_ammo.png", &vars->ammo, &vars->block);
        load_texture_png("BRAINZ_brain.png", &vars->brain, &vars->block);
        load_texture_png("BRAINZ_tnt.png", &vars->bomb, &vars->block);
        load_texture_png("BRAINZ_wall.png", &vars->wall, &vars->block);
        
        load_texture_png("BRAINZ_score.png", &vars->scorename, &vars->block);
        load_texture_png("BRAINZ_scoreback.png", &vars->scoreback, &vars->block);
        load_texture_png("BRAINZ_gameover.png", &vars->gameover, &vars->block);
        load_texture_png("BRAINZ_shadow.png", &vars->shadow, &vars->block);
        
        load_texture_png("BRAINZ_gameover_button1.png", &vars->finish_button[0], &vars->block);
        load_texture_png("BRAINZ_gameover_button2.png", &vars->finish_button[1], &vars->block);
        
        load_texture_png("BRAINZ_OVERREACT.png", &vars->overreact, &vars->block);
        load_texture_png("BRAINZ_title.png", &vars->title, &vars->block);
        load_texture_png("BRAINZ_title_button.png", &vars->title_button, &vars->block);
        load_texture_png("BRAINZ_controls.png", &vars->controls, &vars->block);
        
        Particle_Textures prt_textures;
        
        load_texture_png("prts\\BrainZ_AMMO_part_1.png", &prt_textures.prt_ammo[0], &vars->block);
        load_texture_png("prts\\BrainZ_AMMO_part_2.png", &prt_textures.prt_ammo[1], &vars->block);
        load_texture_png("prts\\BrainZ_AMMO_part_3.png", &prt_textures.prt_ammo[2], &vars->block);
        load_texture_png("prts\\BrainZ_AMMO_part_4.png", &prt_textures.prt_ammo[3], &vars->block);
        load_texture_png("prts\\BrainZ_AMMO_part_5.png", &prt_textures.prt_ammo[4], &vars->block);
        
        load_texture_png("prts\\BrainZ_BLOOD_part_1.png", &prt_textures.prt_blood[0], &vars->block);
        load_texture_png("prts\\BrainZ_BLOOD_part_2.png", &prt_textures.prt_blood[1], &vars->block);
        load_texture_png("prts\\BrainZ_BLOOD_part_3.png", &prt_textures.prt_blood[2], &vars->block);
        load_texture_png("prts\\BrainZ_BLOOD_part_4.png", &prt_textures.prt_blood[3], &vars->block);
        load_texture_png("prts\\BrainZ_BLOOD_part_5.png", &prt_textures.prt_blood[4], &vars->block);
        load_texture_png("prts\\BrainZ_BLOOD_part_6.png", &prt_textures.prt_blood[5], &vars->block);
        
        load_texture_png("prts\\BrainZ_BONE_part_1.png", &prt_textures.prt_bone[0], &vars->block);
        load_texture_png("prts\\BrainZ_BONE_part_2.png", &prt_textures.prt_bone[1], &vars->block);
        load_texture_png("prts\\BrainZ_BONE_part_3.png", &prt_textures.prt_bone[2], &vars->block);
        load_texture_png("prts\\BrainZ_BONE_part_4.png", &prt_textures.prt_bone[3], &vars->block);
        load_texture_png("prts\\BrainZ_BONE_part_5.png", &prt_textures.prt_bone[4], &vars->block);
        load_texture_png("prts\\BrainZ_BONE_part_6.png", &prt_textures.prt_bone[5], &vars->block);
        
        load_texture_png("prts\\BrainZ_BRAIN_part_1.png", &prt_textures.prt_brain[0], &vars->block);
        load_texture_png("prts\\BrainZ_BRAIN_part_2.png", &prt_textures.prt_brain[1], &vars->block);
        load_texture_png("prts\\BrainZ_BRAIN_part_3.png", &prt_textures.prt_brain[2], &vars->block);
        load_texture_png("prts\\BrainZ_BRAIN_part_4.png", &prt_textures.prt_brain[3], &vars->block);
        
        load_texture_png("prts\\BrainZ_HUMAN_part_1.png", &prt_textures.prt_human[0], &vars->block);
        load_texture_png("prts\\BrainZ_HUMAN_part_2.png", &prt_textures.prt_human[1], &vars->block);
        load_texture_png("prts\\BrainZ_HUMAN_part_3.png", &prt_textures.prt_human[2], &vars->block);
        load_texture_png("prts\\BrainZ_HUMAN_part_4.png", &prt_textures.prt_human[3], &vars->block);
        
        load_texture_png("prts\\BrainZ_MUZZLE_part_1.png", &prt_textures.prt_muzzle[0], &vars->block);
        load_texture_png("prts\\BrainZ_MUZZLE_part_2.png", &prt_textures.prt_muzzle[1], &vars->block);
        
        load_texture_png("prts\\BrainZ_TNT_part_1.png", &prt_textures.prt_tnt[0], &vars->block);
        load_texture_png("prts\\BrainZ_TNT_part_2.png", &prt_textures.prt_tnt[1], &vars->block);
        load_texture_png("prts\\BrainZ_TNT_part_3.png", &prt_textures.prt_tnt[2], &vars->block);
        load_texture_png("prts\\BrainZ_TNT_part_4.png", &prt_textures.prt_tnt[3], &vars->block);
        load_texture_png("prts\\BrainZ_TNT_part_5.png", &prt_textures.prt_tnt[4], &vars->block);
        
        load_texture_png("prts\\BrainZ_WALL_part_1.png", &prt_textures.prt_wall[0], &vars->block);
        load_texture_png("prts\\BrainZ_WALL_part_2.png", &prt_textures.prt_wall[1], &vars->block);
        load_texture_png("prts\\BrainZ_WALL_part_3.png", &prt_textures.prt_wall[2], &vars->block);
        load_texture_png("prts\\BrainZ_WALL_part_4.png", &prt_textures.prt_wall[3], &vars->block);
        load_texture_png("prts\\BrainZ_WALL_part_5.png", &prt_textures.prt_wall[4], &vars->block);
        
        load_texture_png("prts\\BrainZ_ZOMBIE_part_1.png", &prt_textures.prt_zombie[0], &vars->block);
        load_texture_png("prts\\BrainZ_ZOMBIE_part_2.png", &prt_textures.prt_zombie[1], &vars->block);
        load_texture_png("prts\\BrainZ_ZOMBIE_part_3.png", &prt_textures.prt_zombie[2], &vars->block);
        load_texture_png("prts\\BrainZ_ZOMBIE_part_4.png", &prt_textures.prt_zombie[3], &vars->block);
        
        load_texture_png("prts\\BrainZ_BULLET_part_1.png", &prt_textures.prt_bullet[0], &vars->block);
        
        i32 prt_tex_count = 0;
        i32 prt_type_count = 0;
        
#define COUNT_PRTS(prt_type) prt_tex_count += ArrayCount(prt_textures.prt_type); ++prt_type_count;
        LIST_PRT_TYPES(COUNT_PRTS);
#undef COUNT_PRTS
        
        vars->prt_type_count = prt_type_count;
        vars->prt_textures = get_mem_array(Render_Texture, &vars->block, prt_tex_count);
        vars->prt_types = get_mem_array(Particle_Type, &vars->block, prt_type_count);
        
        prt_tex_count = 0;
        
        i32 type_i = 0;
#define COUNT_PRTS(prt_type) vars->prt_types[type_i].first_tex_id = prt_tex_count; vars->prt_types[type_i].tex_count = ArrayCount(prt_textures.prt_type); prt_tex_count += ArrayCount(prt_textures.prt_type); ++type_i;
        LIST_PRT_TYPES(COUNT_PRTS);
#undef COUNT_PRTS
        
        Temp_Memory prt_temp = begin_temp_memory(&vars->block);
        Render_Texture **prt_texture_by_type = get_mem_array(Render_Texture*, &vars->block, prt_type_count);
        
        type_i = 0;
#define COUNT_PRTS(prt_type) prt_texture_by_type[type_i++] = prt_textures.prt_type
        LIST_PRT_TYPES(COUNT_PRTS);
#undef COUNT_PRTS
        
        i32 prt_tex_i = 0;
        for (i32 i = 0; i < prt_type_count; ++i){
            i32 count = vars->prt_types[i].tex_count;
            for (i32 j = 0; j < count; ++j){
                vars->prt_textures[prt_tex_i++] = prt_texture_by_type[i][j];
            }
        }
        
        end_temp_memory(prt_temp);
        
        type_i = 0;
#define COUNT_PRTS(t) vars->t##_index = type_i++
        LIST_PRT_TYPES(COUNT_PRTS);
#undef COUNT_PRTS
        
        //load_sound("audtest.wav", &vars->music.sound, target.audio_samples_per_second, &vars->block);
        
        load_sound("SFX\\AmmoFlip_1.wav", &vars->ammo_flip1, target.audio_samples_per_second, &vars->block);
        load_sound("SFX\\AmmoFlip_2.wav", &vars->ammo_flip2, target.audio_samples_per_second, &vars->block);
        load_sound("SFX\\AmmoLand.wav", &vars->ammo_land, target.audio_samples_per_second, &vars->block);
        
        load_sound("SFX\\Brains_1.wav", &vars->brains1, target.audio_samples_per_second, &vars->block);
        load_sound("SFX\\Brains_2.wav", &vars->brains2, target.audio_samples_per_second, &vars->block);
        
        load_sound("SFX\\Explosion.wav", &vars->explosion, target.audio_samples_per_second, &vars->block);
        load_sound("SFX\\GunShot.wav", &vars->gun_shot, target.audio_samples_per_second, &vars->block);
        
        load_sound("SFX\\PersonFlip_1.wav", &vars->person_flip1, target.audio_samples_per_second, &vars->block);
        load_sound("SFX\\PersonFlip_2.wav", &vars->person_flip2, target.audio_samples_per_second, &vars->block);
        load_sound("SFX\\PersonLand.wav", &vars->person_land, target.audio_samples_per_second, &vars->block);
        
        load_sound("SFX\\Reload.wav", &vars->reload, target.audio_samples_per_second, &vars->block);
        
        load_sound("SFX\\SoftFlip_1.wav", &vars->soft_flip1, target.audio_samples_per_second, &vars->block);
        load_sound("SFX\\SoftFlip_2.wav", &vars->soft_flip2, target.audio_samples_per_second, &vars->block);
        load_sound("SFX\\SoftLand_1.wav", &vars->soft_land1, target.audio_samples_per_second, &vars->block);
        load_sound("SFX\\SoftLand_2.wav", &vars->soft_land2, target.audio_samples_per_second, &vars->block);
        
        load_sound("SFX\\WallFlip_1.wav", &vars->wall_flip1, target.audio_samples_per_second, &vars->block);
        load_sound("SFX\\WallFlip_2.wav", &vars->wall_flip2, target.audio_samples_per_second, &vars->block);
        load_sound("SFX\\WallLand.wav", &vars->wall_land, target.audio_samples_per_second, &vars->block);
        
        load_sound("SFX\\ZombieBreak.wav", &vars->zombie_break, target.audio_samples_per_second, &vars->block);
        
        load_sound("SFX\\SplatDeath.wav", &vars->splat_death, target.audio_samples_per_second, &vars->block);
        
        load_sound("SFX\\Score_1.wav", &vars->score1, target.audio_samples_per_second, &vars->block);
        load_sound("SFX\\Score_2.wav", &vars->score2, target.audio_samples_per_second, &vars->block);
        
        load_sound("music\\Menu.wav", &vars->menu_music, target.audio_samples_per_second, &vars->block);
        load_sound("music\\Gameplay_1.wav", &vars->gameplay1, target.audio_samples_per_second, &vars->block);
        load_sound("music\\Gameplay_2.wav", &vars->gameplay2, target.audio_samples_per_second, &vars->block);
        
        load_font("Dimbo Regular.ttf", &vars->font, 36, &vars->block);
        load_font("Dimbo Regular.ttf", &vars->small_font, 32, &vars->block);
        
        game_set_to_new(vars);
        
#if START_WITH_GAME_OVER
        vars->game_over = 1;
        vars->score = 1000000;
#endif
#if SKIP_TITLE_SCREEN
        vars->title_screen = 0;
#endif
    }
    
    // EVERY FRAME
    gj_update(vars->gj);
    
    if (!vars->looked_up_teaser){
        vars->looked_up_teaser = 1;
        
        GJ_Score_Data score_data;
        i32 score_count;
        score_count = gj_get_scores(vars->gj, vars->table_id,
                                    1, &score_data);
        
        if (score_count == 1){
            vars->teaser_score_len = score_data.score_len;
            vars->teaser_name_len = score_data.name_len;
            
            if (vars->teaser_score_len > ArrayCount(vars->teaser_score)){
                vars->teaser_score_len = ArrayCount(vars->teaser_score);
            }
            
            if (vars->teaser_name_len > ArrayCount(vars->teaser_name)){
                vars->teaser_name_len = ArrayCount(vars->teaser_name);
            }
            
            memcpy(vars->teaser_score, score_data.score, vars->teaser_score_len);
            memcpy(vars->teaser_name, score_data.name, vars->teaser_name_len);
            
            gj_free_score(score_data);
        }
        else{
            persist char zero_string[] = "ZERO";
            persist char nobody_string[] = "NOBODY";
            
            vars->teaser_score_len = ArrayCount(zero_string) - 1;
            vars->teaser_name_len = ArrayCount(nobody_string) - 1;
            
            memcpy(vars->teaser_score, nobody_string, vars->teaser_score_len);
            memcpy(vars->teaser_name, nobody_string, vars->teaser_name_len);
        }
    }
    
    if (vars->silence_timer > 0){
        --vars->silence_timer;
    }
    
    if (vars->song_done){
        vars->music = {};
        vars->music.sound = vars->gameplay1;
        vars->music.playing = 1;
        vars->music.looping = 1;
        vars->music.volume = 0.03f;
        vars->music.bend = 1.f;
        vars->song_done = 0;
    }
    
    if (vars->title_screen > 0){
        // DO NOTHING
    }
    else if (vars->controls_screen){
        // DO NOTHING
    }
    else{
        i32 prev_score = vars->score;
        if (!vars->game_over){
            
            // TODO(allen): allow holding but slow it down
            bool32 left = input.digital.left && !vars->prev_input.digital.left;
            bool32 right = input.digital.right && !vars->prev_input.digital.right;
            bool32 rot_left = input.button[1] && !vars->prev_input.button[1];
            bool32 rot_right = input.button[2] && !vars->prev_input.button[2];
            
            for (i32 i = 0; i < vars->entity_count; ++i){
                Entity *entity = vars->entities + i;
                
                if (entity->active){
                    if (left && !right){
                        i32 left = entity->grid_x - 1;
                        if (can_move_to(vars, entity, left, entity->grid_y)){
                            move_entity(vars, entity, left, entity->grid_y);
                        }
                    }
                    else if (right && !left){
                        i32 right = entity->grid_x + 1;
                        if (can_move_to(vars, entity, right, entity->grid_y)){
                            move_entity(vars, entity, right, entity->grid_y);
                        }
                    }
                    
                    bool32 did_rotation = 0;
                    if (rot_left && !rot_right){
                        entity->facing += 1;
                        entity->facing %= 4;
                        did_rotation = 1;
                    }
                    else if (rot_right && !rot_left){
                        entity->facing += 3;
                        entity->facing %= 4;
                        did_rotation = 1;
                    }
                    
                    if (did_rotation){
                        entity->wobble = TAU32 / 10.f;
                        
                        Sound *snds[2];
                        switch (entity->type){
                        case AMMO:
                        {
                            snds[0] = &vars->ammo_flip1;
                            snds[1] = &vars->ammo_flip2;
                        }break;
                        
                        case HUMAN:
                        case ZOMBIE:
                        {
                            snds[0] = &vars->person_flip1;
                            snds[1] = &vars->person_flip2;
                        }break;
                        
                        case BRAIN:
                        {
                            snds[0] = &vars->soft_flip1;
                            snds[1] = &vars->soft_flip2;
                        }break;
                        
                        case WALL:
                        case BOMB:
                        {
                            snds[0] = &vars->wall_flip1;
                            snds[1] = &vars->wall_flip2;
                        }break;
                        }
                        
                        i32 which = (pcg32_random_r(&vars->rnd) % 2);
                        
                        play_sound_effect(vars, *snds[which],
                                          1.f, random_real32(vars, BEND_DOWN, BEND_UP));
                    }
                }
            }
        
            if (!vars->chain_reacting){
                bool32 board_check = 0;
                if (vars->need_to_fill_gaps){
                    if (vars->fill_timer > 0){
                        --vars->fill_timer;
                    }
                    else{
                        // FILL DOWN
                        Temp_Memory temp = begin_temp_memory(&vars->block);
                    
                        Entity_Y_Data *data = get_mem_array(Entity_Y_Data, &vars->block, vars->entity_count);
                        for (i32 i = 0; i < vars->entity_count; ++i){
                            data[i].index = i;
                            data[i].y = vars->entities[i].grid_y;
                        }
                    
                        quick_sort(data, 0, vars->entity_count-1);
                    
                        vars->need_to_fill_gaps = 0;
                        for (i32 i = 0; i < vars->entity_count; ++i){
                            Entity *entity = vars->entities + data[i].index;
                        
                            if (can_move_to(vars, entity, entity->grid_x, entity->grid_y + 1)){
                                move_entity(vars, entity, entity->grid_x, entity->grid_y + 1);
                                vars->need_to_fill_gaps = 1;
                            }
                        }
                    
                        if (!vars->need_to_fill_gaps){
                            board_check = 1;
                        }
                        else{
                            vars->fill_timer = COMBO_TIME;
                        }
                    
                        end_temp_memory(temp);
                    }
                }
                else if (!vars->need_new_block){
                    // FALL TICK
                    if (input.digital.down){
                        if (vars->fall_timer > 4){
                            vars->fall_timer = 4;
                        }
                    }
                    
                    bool32 land_now = 0;
                    --vars->fall_timer;
                    
                    for (i32 i = 0; i < vars->entity_count; ++i){
                        Entity *entity = vars->entities + i;
                        
                        if (entity->active){
                            if (vars->fall_timer <= 0){
                                vars->fall_timer += get_level_fall_tick_time(vars, vars->level);
                                
                                if (can_move_to(vars, entity, entity->grid_x, entity->grid_y + 1)){
                                    move_entity(vars, entity, entity->grid_x, entity->grid_y + 1);
                                }
                                else{
                                    land_now = 1;
                                }
                            }
                            else{
                                if (!can_move_to(vars, entity, entity->grid_x, entity->grid_y + 1) &&
                                    entity->show_x == entity->grid_x && entity->show_y == entity->grid_y){
                                    land_now = 1;
                                }
                            }
                            
                            if (land_now){
                                Sound *snd = 0;
                                switch (entity->type){
                                case AMMO:
                                {
                                    snd = &vars->ammo_land;
                                }break;
                        
                                case HUMAN:
                                case ZOMBIE:
                                {
                                    snd = &vars->person_land;
                                }break;
                        
                                case BRAIN:
                                {
                                    if ((pcg32_random_r(&vars->rnd) % 2) == 0){
                                        snd = &vars->soft_land1;
                                    }
                                    else{
                                        snd = &vars->soft_land2;
                                    }
                                }break;
                                    
                                case WALL:
                                {
                                    if ((pcg32_random_r(&vars->rnd) % 2) == 0){
                                        snd = &vars->wall_land;
                                    }
                                    else{
                                        snd = &vars->wall_flip1;
                                    }
                                }break;
                                    
                                case BOMB:
                                {
                                    snd = &vars->person_flip2;
                                }break;
                                }

                                if (snd){
                                    play_sound_effect(vars, *snd,
                                                      1.f, random_real32(vars, BEND_DOWN, BEND_UP));
                                }
                                    
                                entity->active = 0;
                                entity->wobble = 0;
                                if (entity->grid_y == 0){
                                    vars->game_over = 1;
                                }
                                vars->need_new_block = 1;
                                board_check = 1;
                            }
                        }
                    }
                }
            
                // SPREAD INFECTION
                for (i32 i = 0; i < vars->entity_count; ++i){
                    Entity *entity = vars->entities + i;
                    if (entity->type == HUMAN){
                        Grid_Pos center = get_grid_pos(entity);
                    
                        i32 infection_counter = 0;
                        for (i32 i = 0; i < ArrayCount(von_neumann); ++i){
                            Grid_Pos pos = center + von_neumann[i];
                            Entity *neighbor = get_grid_entity(vars, pos.x, pos.y);
                            if (neighbor){
                                if (neighbor->type == ZOMBIE){
                                    ++infection_counter;
                                }
                            }
                        }
                    
                        if (infection_counter == 0){
                            if (entity->infection_amount > 0){
                                entity->infection_amount -= 1;
                            }
                        }
                        else{
                            entity->infection_amount += infection_counter;
                        }
                    
                        if (entity->infection_amount > ZOMBIE_TURN_THRESHOLD){
                            entity->type = ZOMBIE;
                            entity->facing += 3;
                            entity->facing %= 4;
                            board_check = 1;
                        }
                    }
                }
            
                // BOARD CHECK
                if (board_check){
                    for (i32 i = 0; i < vars->entity_count; ++i){
                        Entity *entity = vars->entities + i;
                    
                        switch (entity->type){
                        case AMMO:
                        {
                            bool32 ammo_used = 0;
                            Grid_Pos center = get_grid_pos(entity);
                            for (i32 i = 0; i < ArrayCount(von_neumann); ++i){
                                Grid_Pos pos = center + von_neumann[i];
                            
                                Entity *neighbor = get_grid_entity(vars, pos.x, pos.y);
                                if (neighbor){
                                    if (neighbor->type == HUMAN){
                                        ammo_used = 1;
                                        neighbor->firing = 1;
                                    }
                                }
                            }
                            
                            if (ammo_used){
                                play_sound_effect(vars, vars->reload,
                                                  0.5f, random_real32(vars, BEND_DOWN, BEND_UP));
                                mark_block_destroyed(vars, entity);
                            }
                        }break;
                    
                        case BRAIN:
                        {
                            bool32 brain_eaten = 0;
                            Grid_Pos center = get_grid_pos(entity);
                            for (i32 i = 0; i < ArrayCount(von_neumann); ++i){
                                Grid_Pos pos = center + von_neumann[i];
                            
                                Entity *neighbor = get_grid_entity(vars, pos.x, pos.y);
                                if (neighbor){
                                    if (neighbor->type == ZOMBIE){
                                        brain_eaten = 1;
                                        neighbor->firing = 1;
                                    }
                                }
                            }
                            
                            if (brain_eaten){
                                mark_block_destroyed(vars, entity);
                            }
                        }break;
                        }
                    }
                }
            }

            // RANDOM BRAINS
            bool32 has_zombie = 0;
            for (i32 i = 0; i < vars->entity_count; ++i){
                Entity *entity = vars->entities + i;
                
                if (entity->type == ZOMBIE){
                    has_zombie = 1;
                    break;
                }
            }
            
            if (has_zombie && (pcg32_random_r(&vars->rnd) % 600) == 0){
                Sound *snd;
                if ((pcg32_random_r(&vars->rnd) % 2) == 0){
                    snd = &vars->brains1;
                }
                else{
                    snd = &vars->brains2;
                }
                
                play_sound_effect(vars, *snd,
                                  1.f, random_real32(vars, BEND_DOWN, BEND_UP));
            }
            
            // FIRE
            if (vars->reaction_timer > 0){
                --vars->reaction_timer;
            }
            else{
                vars->chain_reacting = 0;
                Temp_Memory gun_temp = begin_temp_memory(&vars->block);
                i32 reaction_fire_count = 0;
                i32 reaction_fire_max = WIDTH*HEIGHT;
                Entity **reaction_fire = get_mem_array(Entity*, &vars->block, reaction_fire_max);
                for (i32 i = 0; i < vars->entity_count; ++i){
                    Entity *entity = vars->entities + i;
                    Assert(!(entity->firing && entity->step_forward));
                    if (entity->firing){
                        vars->chain_reacting = 1;
                        vars->reaction_timer = COMBO_TIME;
                        entity->firing = 0;
                        if (entity->type == HUMAN || entity->type == AMMO){
                            i32 step_count = ArrayCount(up_shot);
                            Grid_Pos *shot_pos = right_shot;
                            switch (entity->facing){
                            case UP:
                                shot_pos = up_shot;
                                break;
                            
                            case LEFT:
                                shot_pos = left_shot;
                                break;
                            
                            case DOWN:
                                shot_pos = down_shot;
                                break;
                            }
                        
                            Grid_Pos center = get_grid_pos(entity);
                            for (i32 i = 0; i < step_count; ++i){
                                Grid_Pos pos = center + shot_pos[i];
                            
                                bool32 out_of_grid;
                                Entity *hit_entity = get_grid_entity(vars, pos.x, pos.y, &out_of_grid);
                                if (out_of_grid){
                                    break;
                                }
                                if (hit_entity){
                                    if (hit_entity->type == WALL){
                                        break;
                                    }
                                    else{
                                        switch (hit_entity->type){
                                        case HUMAN: case ZOMBIE: case BRAIN:
                                        {
                                            mark_block_destroyed(vars, hit_entity);
                                        }break;
                                    
                                        case AMMO: case BOMB:
                                        {
                                            Assert(reaction_fire_count < reaction_fire_max);
                                            reaction_fire[reaction_fire_count++] = hit_entity;
                                        }break;
                                        }
                                    }
                                }
                            }
                            
                            play_sound_effect(vars, vars->gun_shot,
                                              0.5f, random_real32(vars, BEND_DOWN, BEND_UP));
                            
                            if (entity->type == AMMO){
                                mark_block_destroyed(vars, entity);
                            }
                            if (entity->type == HUMAN){
                                real32 rotation = 0.f;
                                Vec2 pos = get_screen_pos(entity);
                                persist real32 MUZZLE_HALF_WIDTH = 70.f;
                            
                                switch (entity->facing){
                                case RIGHT:
                                    rotation = 0.f;
                                    pos.x += 43 + MUZZLE_HALF_WIDTH;
                                    pos.y += 36;
                                    break;
                                
                                case UP:
                                    rotation = 270.f;
                                    pos.x += 36;
                                    pos.y += -43 - MUZZLE_HALF_WIDTH;
                                    break;
                                
                                case LEFT:
                                    rotation = 180.f;
                                    pos.x += -43 - MUZZLE_HALF_WIDTH;
                                    pos.y += -36;
                                    break;
                                
                                case DOWN:
                                    rotation = 90.f;
                                    pos.x += -36;
                                    pos.y += 43 + MUZZLE_HALF_WIDTH;
                                    break;
                                }
                            
                                spawn_particles_type_3(vars, vars->prt_muzzle_index, pos, rotation);
                            }
                        }
                    
                        else if (entity->type == BOMB){
                            Grid_Pos center = get_grid_pos(entity);
                            for (i32 i = 0; i < ArrayCount(moore); ++i){
                                Grid_Pos pos = center + moore[i];
                                
                                Entity *hit_entity = get_grid_entity(vars, pos.x, pos.y);
                                if (hit_entity){
                                    switch (hit_entity->type){
                                    case HUMAN: case ZOMBIE: case BRAIN: case WALL:
                                    {
                                        mark_block_destroyed(vars, hit_entity);
                                    }break;
                                    
                                    case AMMO: case BOMB:
                                    {
                                        Assert(reaction_fire_count < reaction_fire_max);
                                        reaction_fire[reaction_fire_count++] = hit_entity;
                                    }break;
                                    }
                                }
                                
                                play_sound_effect(vars, vars->explosion,
                                                  0.03f, random_real32(vars, BEND_DOWN, BEND_UP));
                            }
                            mark_block_destroyed(vars, entity);
                        }
                    
                        else if (entity->type == ZOMBIE){
                            Grid_Pos pos = get_grid_pos(entity);
                            switch (entity->facing){
                            case RIGHT:
                                pos.x += 1;
                                break;
                                
                            case UP:
                                pos.y -= 1;
                                break;
                                
                            case LEFT:
                                pos.x -= 1;
                                break;
                                
                            case DOWN:
                                pos.y += 1;
                                break;
                            }
                            
                            bool32 out_of_grid;
                            Entity *target = get_grid_entity(vars, pos.x, pos.y, &out_of_grid);
                            if (!out_of_grid){
                                if (target){
                                    switch (target->type){
                                    case BOMB: case AMMO:
                                    {
                                        Assert(reaction_fire_count < reaction_fire_max);
                                        reaction_fire[reaction_fire_count++] = target;
                                    }break;
                                
                                    default:
                                    {
                                        mark_block_destroyed(vars, target);
                                    }break;
                                    }
                                }
                                entity->step_forward = 3;
                                play_sound_effect(vars, vars->zombie_break,
                                                  0.1f, random_real32(vars, BEND_DOWN, BEND_UP));
                            }
                        }
                    
                        else{
                            Assert(1);
                        }
                    }
                }
                
                for (i32 i = 0; i < vars->entity_count; ++i){
                    Entity *entity = vars->entities + i;
                    Assert(!(entity->firing && entity->step_forward));
                    if (entity->step_forward){
                        --entity->step_forward;
                        vars->chain_reacting = 1;
                        vars->reaction_timer = COMBO_TIME;
                        Assert(entity->type == ZOMBIE);
                    
                        Grid_Pos pos = get_grid_pos(entity);
                        switch (entity->facing){
                        case RIGHT:
                            pos.x += 1;
                            break;
                        
                        case UP:
                            pos.y -= 1;
                            break;
                        
                        case LEFT:
                            pos.x -= 1;
                            break;
                        
                        case DOWN:
                            pos.y += 1;
                            break;
                        }
                        
                        bool32 out_of_grid;
                        if (can_move_to(vars, entity, pos.x, pos.y, &out_of_grid)){
                            move_entity(vars, entity, pos.x, pos.y);
                            entity->step_forward = 0;
                            
                            Sound *snd;
                            if ((pcg32_random_r(&vars->rnd) % 2) == 0){
                                snd = &vars->brains1;
                            }
                            else{
                                snd = &vars->brains2;
                            }
                            
                            play_sound_effect(vars, *snd,
                                              1.f, random_real32(vars, BEND_DOWN, BEND_UP));
                        }
                        if (out_of_grid){
                            entity->step_forward = 0;
                        }
                    }
                }
            
                for (i32 i = 0; i < reaction_fire_count; ++i){
                    reaction_fire[i]->firing = 1;
                }
            
                end_temp_memory(gun_temp);
            }
        
            // TRY TO SPAWN BLOCK
            if (vars->need_new_block && !vars->need_to_fill_gaps && !vars->chain_reacting){
                make_random_block(vars);
                vars->need_new_block = 0;
            }
        
            // DESTROY OBJECTS
            Assert(vars->free_count <= vars->entity_count);
            quick_sort((void**)vars->to_free, 0, vars->free_count-1);
            for (i32 i = 0; i < vars->free_count; ++i){
                Entity *entity = vars->to_free[i];
                
                if (entity->active){
                    vars->need_new_block = 1;
                }
                
                i32 entity_index = get_entity_index(vars, entity);
                
                bool32 play_splat_death = 0;
                
                switch (entity->type){
                case HUMAN:
                {
                    spawn_particles_type_1(vars, vars->prt_human_index, get_screen_pos(entity), 8);
                    spawn_particles_type_1(vars, vars->prt_bone_index, get_screen_pos(entity), 6);
                    spawn_particles_type_1(vars, vars->prt_blood_index, get_screen_pos(entity), 15);
                    spawn_particles_type_1(vars, vars->prt_brain_index, get_screen_pos(entity), 2);
                    
                    play_splat_death = 1;
                }break;
                
                case ZOMBIE:
                {
                    spawn_particles_type_1(vars, vars->prt_zombie_index, get_screen_pos(entity), 8);
                    spawn_particles_type_1(vars, vars->prt_bone_index, get_screen_pos(entity), 6);
                    spawn_particles_type_1(vars, vars->prt_blood_index, get_screen_pos(entity), 15);
                    spawn_particles_type_1(vars, vars->prt_brain_index, get_screen_pos(entity), 2);
                    
                    play_splat_death = 1;
                }break;
                
                case AMMO:
                {
                    spawn_particles_type_2(vars, vars->prt_ammo_index, get_screen_pos(entity), 12);
                }break;
                
                case BRAIN:
                {
                    spawn_particles_type_1(vars, vars->prt_brain_index, get_screen_pos(entity), 6);
                    spawn_particles_type_1(vars, vars->prt_blood_index, get_screen_pos(entity), 15);
                    
                    play_splat_death = 1;
                }break;
                
                case WALL:
                {
                    spawn_particles_type_1(vars, vars->prt_wall_index, get_screen_pos(entity), 12);
                }break;
                
                case BOMB:
                {
                    spawn_particles_type_1(vars, vars->prt_tnt_index, get_screen_pos(entity), 12);
                }break;
                }
                
                if (play_splat_death){
                    play_sound_effect(vars, vars->splat_death,
                                      1.f, random_real32(vars, BEND_DOWN, BEND_UP));
                }
                
                vars->grid[get_i(entity->grid_x, entity->grid_y)] = 0;
                --vars->entity_count;
                if (vars->entity_count > entity_index){
                    Entity *end_entity = vars->entities + vars->entity_count;
                    vars->grid[get_i(end_entity->grid_x, end_entity->grid_y)] = entity;
                    *entity = *end_entity;
                }
            }
            vars->free_count = 0;
            
            // SPAWN OBJECTS
            for (i32 i = 0; i < vars->spawn_count; ++i){
                Spawn_Request spawn = vars->spawns[i];
                
                i32 x, y;
                Block_Type type;
                i32 facing;
                if (spawn.random){
                    x = pcg32_random_r(&vars->rnd) % WIDTH;
                    y = 0;
                    
#if TEST_ORDER == 0
                    //type = (Block_Type)(ZOMBIE + pcg32_random_r(&vars->rnd) % type_count);
                    type = block_freq_table[pcg32_random_r(&vars->rnd) % ArrayCount(block_freq_table)];
#else
                    Block_Type test_types[] = {
                        AMMO,
                        HUMAN
                    };
                
                    type = test_types[vars->test_type_i++];
                    vars->test_type_i = vars->test_type_i % ArrayCount(test_types);
#endif
                    facing = (i32)(pcg32_random_r(&vars->rnd) % 4);
                }
                else{
                    x = spawn.x;
                    y = spawn.y;
                    type = spawn.type;
                    facing = (i32)(pcg32_random_r(&vars->rnd) % 4);
                }
            
                if (vars->grid[get_i(x, y)] == 0){
                    Entity *entity = spawn_entity(vars);
                    entity->type = type;
                    entity->grid_x = x;
                    entity->grid_y = y;
                    entity->show_x = (real32)x;
                    entity->show_y = (real32)y;
                    entity->active = 1;
                    entity->facing = facing;
                
                    vars->grid[get_i(x, y)] = entity;
                }
                else{
                    vars->game_over = 1;
                }
            }
        
            vars->spawn_count = 0;
        }
    
        // CHECK EFFECTS FROM POINTS
        i32 next_level = vars->level + 1;
        if (vars->score >= get_level_min_score(vars, next_level)){
            vars->level = next_level;
        }
        
        i32 score_increase = vars->score - prev_score;
        
        if (score_increase > 0){
            Sound *snd;
            if (score_increase >= 100){
                snd = &vars->score1;
            }
            else{
                snd = &vars->score2;
            }
            play_sound_effect(vars, *snd,
                              1.1f, random_real32(vars, BEND_DOWN, BEND_UP),
                              15);
        }
    
    }
    
    // RENDERING
    Vec2 screen_center;
    screen_center.x = (real32)target.render.width * 0.5f;
    screen_center.y = (real32)target.render.height * 0.5f;
    
    if (vars->title_screen > 0){
        Vec4 black;
        black = V4(0.f, 0.f, 0.f, 1.f);
        draw_clear(black);

        
        persist i32 PHASE_0 = 120;
        persist i32 PHASE_1 = 150;
        persist i32 PHASE_2 = 210;
        persist i32 PHASE_3 = 330;
        persist i32 PHASE_4 = 400;
        
        if (vars->title_screen < PHASE_4){
            ++vars->title_screen;
        }
        
        if (vars->title_screen < PHASE_0){
            real32 a = (vars->title_screen / (real32)(PHASE_0));
            
            Vec4 color = V4(1.f, 1.f, 1.f, a);
            draw_texture(vars->overreact, screen_center, screen_center, 0.f, color);
        }
        else if (vars->title_screen < PHASE_1){
            Vec4 color = V4(1.f, 1.f, 1.f, 1.f);
            draw_texture(vars->overreact, screen_center, screen_center, 0.f, color);
        }
        else if (vars->title_screen < PHASE_2){
            real32 a = ((PHASE_2 - vars->title_screen) / (real32)(PHASE_2 - PHASE_1));
            
            Vec4 color = V4(1.f, 1.f, 1.f, a);
            draw_texture(vars->overreact, screen_center, screen_center, 0.f, color);
        }
        else{
            real32 a = 1.f;
            if (vars->title_screen < PHASE_3){
                a = ((vars->title_screen - PHASE_2) / (real32)(PHASE_3 - PHASE_2));
            }
            
            Vec4 color = V4(1.f, 1.f, 1.f, a);
            draw_texture(vars->title, screen_center, screen_center, 0.f, color);
            
            if (vars->title_screen >= PHASE_3){
                a = 1.f;
                if (vars->title_screen < PHASE_4){
                    a = ((vars->title_screen - PHASE_3) / (real32)(PHASE_4 - PHASE_3));
                }
                
                if (input.button[0]){
                    vars->title_screen = 0;
                    vars->controls_screen = 1;
                    
                    vars->music = {};
                    vars->music.sound = vars->gameplay2;
                    vars->music.playing = 1;
                    vars->music.volume = 0.03f;
                    vars->music.bend = 1.f;
                }
                
                color = V4(1.f, 1.f, 1.f, a);
                draw_texture(vars->title_button, V2(400.f, 420.f), 0.f, color);
            }
        }
    }
    else{
        draw_texture(vars->background, screen_center, screen_center);
    
        draw_texture(vars->scoreback, V2(110.f, 80.f));
        draw_texture(vars->scorename, V2(110.f, 50.f));
    
        char score_string[16];
        char post_string[16];
        persist char mil_string[] = "MILLION";
    
        i32 i, j;
        i = int_to_string(score_string, vars->score);
        j = int_to_string(post_string, vars->nonsense_score, 3);
        
        real32 text_x, text_y, start_x, end_x;
        text_x = 20.f;
        text_y = 100.f;
        start_x = 20.f;
        end_x = 180.f;
        
        Vec4 white = {1.f, 1.f, 1.f, 1.f};
        AllowLocal(white);
        Vec4 red = {1.f, 0.f, 0.f, 1.f};
    
        draw_text(&target.render, &vars->small_font, &text_x, &text_y, score_string + i, 16 - i, red, start_x, end_x);
        if (vars->score > 0){
            draw_text(&target.render, &vars->small_font, &text_x, &text_y, post_string + j, 16 - j, red, start_x, end_x);
            text_x = start_x;
            text_y += vars->small_font.height;
            draw_text(&target.render, &vars->small_font, &text_x, &text_y, mil_string, ArrayCount(mil_string) - 1, red, start_x, end_x);
        }
        
        text_x = 20.f;
        text_y = 300.f;
        start_x = 20.f;
        end_x = 180.f;
    
        persist char level_label[] = "LEVEL: ";
        draw_text(&target.render, &vars->small_font, &text_x, &text_y, level_label, ArrayCount(level_label) - 1, red, start_x, end_x);
    
        char level_string[16];
        i = int_to_string(level_string, vars->level + 1);
        draw_text(&target.render, &vars->small_font, &text_x, &text_y, level_string + i, 16 - i, red, start_x, end_x);
    
        text_x = 20.f;
        text_y = 475.f;
        start_x = 20.f;
        end_x = 180.f;
    
        persist char best_label[] = "THE MOST SCORE";
        draw_text(&target.render, &vars->small_font, &text_x, &text_y, best_label, ArrayCount(best_label) - 1, red, start_x, end_x);
        text_x = start_x;
        text_y += vars->small_font.height;
        draw_text(&target.render, &vars->small_font, &text_x, &text_y, vars->teaser_score, vars->teaser_score_len, red, start_x, end_x);
        text_x = start_x;
        text_y += vars->small_font.height;
        draw_text(&target.render, &vars->small_font, &text_x, &text_y, vars->teaser_name, vars->teaser_name_len, red, start_x, end_x);
    
        for (i32 i = 0; i < vars->entity_count; ++i){
            Entity *entity = vars->entities + i;
        
            persist real32 BLOCK_LERP_SPEED = 0.5f;
        
            if (entity->show_x != entity->grid_x){
                entity->show_x = lerp(entity->show_x, BLOCK_LERP_SPEED, (real32)entity->grid_x);
                if (abs(entity->show_x - entity->grid_x) < 0.05){
                    entity->show_x = (real32)(entity->grid_x);
                }
            }
        
            if (entity->show_y != entity->grid_y){
                entity->show_y = lerp(entity->show_y, BLOCK_LERP_SPEED, (real32)entity->grid_y);
                if (abs(entity->show_y - entity->grid_y) < 0.05){
                    entity->show_y = (real32)(entity->grid_y);
                }
            }
            
            real32 x, y;
            x = entity->show_x;
            y = entity->show_y;
            
            Render_Texture *texture = 0;
            switch (entity->type){
            case ZOMBIE:
                texture = &vars->zombie[0];
                break;
            
            case HUMAN:
            {
                i32 infection_level = (entity->infection_amount * 4) / ZOMBIE_TURN_THRESHOLD;
                texture = &vars->human[0][infection_level];
            }break;
        
            case BRAIN:
                texture = &vars->brain;
                break;
            
            case AMMO:
                texture = &vars->ammo;
                break;
            
            case BOMB:
                texture = &vars->bomb;
                break;
            
            case WALL:
                texture = &vars->wall;
                break;
            }
        
            Vec2 center = get_screen_pos(entity);
        
            persist real32 SCALE_DOWN = 0.3168316f;
        
            Vec2 halfdim;
            halfdim.x = texture->img_width * .5f * SCALE_DOWN;
            halfdim.y = texture->img_height * .5f * SCALE_DOWN;
        
            real32 rotation = 0.f;
            if (entity->type == ZOMBIE){
                switch (entity->facing){
                case RIGHT:
                    rotation = 0.f;
                    break;
                
                case UP:
                    rotation = 270.f;
                    break;
                
                case LEFT:
                    rotation = 180.f;
                    break;
                
                case DOWN:
                    rotation = 90.f;
                    break;
                }
            }
            
            else{
                switch (entity->facing){
                case RIGHT:
                    rotation = 90.f;
                    break;
                    
                case UP:
                    rotation = 0.f;
                    break;
                
                case LEFT:
                    rotation = 270.f;
                    break;
                
                case DOWN:
                    rotation = 180.f;
                    break;
                }
            }
            
            if (entity->active){
                rotation += 10.f*sinf(entity->wobble);
                if (entity->wobble > 0.f && entity->wobble < TAU32){
                    entity->wobble += TAU32 / 5.f;
                }
                else{
                    entity->wobble = 0;
                }
            }
            
            if (entity->firing){
                switch (entity->type){
                case BOMB:
                {
                    halfdim *= 1.1f;
                    rotation += 10.f*sinf(entity->wobble);
                    entity->wobble += TAU32 / 5.f;
                
                    draw_texture(*texture, center, halfdim, rotation);
                }break;
                
                case AMMO:
                {
                    Vec4 color = V4(1.f, 1.f, 1.f, 1.f);
                    color.g = abs(sinf(entity->wobble))*0.5f + 0.5f;
                    color.b = color.g;
                    entity->wobble += TAU32 / 20.f;
                    
                    draw_texture(*texture, center, halfdim, rotation, color);
                }break;
                
                default:
                {
                    draw_texture(*texture, center, halfdim, rotation);
                }break;
                }
            }
            else if (entity->step_forward){
                halfdim *= (1.f + sinf(entity->wobble)*.15f + .15f);
                entity->wobble += TAU32 / 25.f;
                
                draw_texture(*texture, center, halfdim, rotation);
            }
            else{
                draw_texture(*texture, center, halfdim, rotation);
            }
        }
    
        // PARTICLE RENDER
        Particle *part = vars->particles;
        Particle *end_part = vars->particles + vars->particle_count;
        Render_Texture *part_texs = vars->prt_textures;
        for (; part < end_part;){
            if ((--part->life_counter) > 0){
                part->pos += part->vel;
                part->rot += part->rot_vel;
                part->vel.y += 0.5f;
            
                if (part->pos.y < 700.f){
                    draw_texture(part_texs[part->tex_index], part->pos, part->rot);
                    ++part;
                }
                else{
                    --end_part;
                    *part = *end_part;
                }
            }
            else{
                --end_part;
                *part = *end_part;
            }
        }
        vars->particle_count = (i32)(end_part - vars->particles);
        Assert(vars->particle_count >= 0);
        
        draw_texture(vars->shadow, screen_center, screen_center, 0.f, V4(1.f, 1.f, 1.f, 0.4f));

        // CONTROLS SCREEN RENDER
        if (vars->controls_screen){
            draw_rectangle(0.f, 0.f, screen_center.x*2, screen_center.y*2,
                           V4(0.f, 0.f, 0.f, 0.5f));
            draw_texture(vars->controls, screen_center, screen_center);
            
            i32 field_index = 0;
            if (do_button(field_index++, vars->active_field,
                          input, vars->finish_button,
                          690.f, 500.f)){
                vars->controls_screen = 0;
            }
        
            if (input.digital.up && !vars->prev_input.digital.up){
                vars->active_field += (field_index - 1);
                vars->active_field %= field_index;
            }
        
            if (input.digital.down && !vars->prev_input.digital.down){
                vars->active_field += 1;
                vars->active_field %= field_index;
            }
        }
        
        // GAME OVER RENDER
        else if (vars->game_over){
            draw_rectangle(0.f, 0.f, screen_center.x*2, screen_center.y*2,
                           V4(0.f, 0.f, 0.f, 0.5f));
            draw_texture(vars->gameover, screen_center, screen_center);
        
            i32 field_index = 0;
            bool32 blink_on = (vars->blink_timer < 15);
            vars->blink_timer = (vars->blink_timer + 1) % 30;
        
            bool32 move_down = 0;
        
            if (do_text_field(field_index++, vars->active_field, blink_on,
                              &target.render, input,
                              &vars->font, 305.f, 285.f,
                              &vars->user_name_len, vars->user_name, 15)){
                move_down = 1;
            }
        
            if (do_text_field(field_index++, vars->active_field, blink_on,
                              &target.render, input,
                              &vars->font, 305.f, 342.f,
                              &vars->user_token_len, vars->user_token, 15)){
                move_down = 1;
            }
        
            if (do_button(field_index++, vars->active_field,
                          input, vars->finish_button,
                          690.f, 500.f)){
                if (vars->user_name_len != 0){
                    vars->user_name[vars->user_name_len] = 0;
                    vars->user_token[vars->user_token_len] = 0;
                    
                    char score_string[64];
                    i32 i = 64, j;
                    if (vars->score > 0){
                        score_string[--i] = 0;
                        persist char short_mil_string[] = " MIL";
                        for (i32 k = ArrayCount(short_mil_string) - 2; k >= 0; --k){
                            score_string[--i] = short_mil_string[k];
                        }
                    
                        j = int_to_string(score_string + i - 16, vars->nonsense_score, 3);
                        j = (16 - j);
                        i -= j;
                        
                        j = int_to_string(score_string + i - 16, vars->score);
                        j = (16 - j);
                        i -= j;
                    }
                    else{
                        persist char zero_string[] = "ZERO";
                        score_string[--i] = 0;
                        for (i32 j = ArrayCount(zero_string) - 2; j >= 0; --j){
                            score_string[--i] = zero_string[j];
                        }
                    }
                
                    bool32 guest_score = 0;
                    if (vars->user_token_len != 0){
                        if (gj_login(vars->gj, vars->user_name, vars->user_token)){
                            gj_post_score(vars->gj, vars->table_id, score_string + i, vars->score, "", "");
                            gj_logout(vars->gj);
                        }
                        else{
                            guest_score = 1;
                        }
                    }
                    else{
                        guest_score = 1;
                    }
                
                    if (guest_score){
                        gj_post_score(vars->gj, vars->table_id, score_string + i, vars->score, "", vars->user_name);
                    }
                }
                
                game_set_to_new(vars);
            }
        
            if (input.digital.up && !vars->prev_input.digital.up){
                vars->active_field += (field_index - 1);
                vars->active_field %= field_index;
            }
        
            if ((input.digital.down && !vars->prev_input.digital.down) || move_down){
                vars->active_field += 1;
                vars->active_field %= field_index;
            }
        }
    }
    
    vars->prev_input = input;
    
    return 0;
}

// BOTTOM
