740 lines
19 KiB
C
740 lines
19 KiB
C
#include <assert.h>
|
|
#include <stdio.h>
|
|
|
|
#include <maxmod9.h>
|
|
#include <nds.h>
|
|
|
|
#include "background.h"
|
|
#include "sprite.h"
|
|
#include "soundbank.h"
|
|
#include "soundbank_bin.h"
|
|
|
|
#include "constants.h"
|
|
#include "units.h"
|
|
#include "types.h"
|
|
#include "ptrmath.h"
|
|
#include "credits.h"
|
|
|
|
constexpr int playfield_margin = 1 * tile_side;
|
|
constexpr struct rect playfield = {
|
|
{playfield_margin, playfield_margin},
|
|
SCREEN_WIDTH - 2 * playfield_margin,
|
|
SCREEN_HEIGHT - 2 * playfield_margin
|
|
};
|
|
|
|
constexpr int screen_height_chars = SCREEN_HEIGHT / character_side;
|
|
|
|
constexpr struct rect grid_rect = {
|
|
{0, 0},
|
|
SCREEN_WIDTH / tile_side - 2,
|
|
SCREEN_HEIGHT / tile_side - 2
|
|
};
|
|
enum bg_tiles grid[grid_rect.h][grid_rect.w];
|
|
|
|
int undeployed_units[PLAYERS_COUNT][UNIT_TYPES_COUNT] = {
|
|
[P1] = {
|
|
[UNIT_GRASS] = 0,
|
|
[UNIT_WALKER] = 2,
|
|
},
|
|
[P2] = {
|
|
[UNIT_GRASS] = 5,
|
|
[UNIT_WALKER] = 6,
|
|
},
|
|
};
|
|
|
|
enum unit_types unit_to_place;
|
|
|
|
int left_to_deploy(enum players player) {
|
|
int left = 0;
|
|
for (int i = 0; i < UNIT_TYPES_COUNT; i++) {
|
|
left += undeployed_units[player][i];
|
|
}
|
|
return left;
|
|
}
|
|
|
|
bool can_player_deploy(struct pos pos, enum players player, enum unit_types type) {
|
|
return (grid[pos.y][pos.x] == PLAYFIELD || grid[pos.y][pos.x] == player_playfield_tile[player]) &&
|
|
undeployed_units[player][type] > 0;
|
|
}
|
|
|
|
int mainBg, subBg;
|
|
|
|
u16 *arrows[9];
|
|
|
|
struct tile tiles[BG_TILES_COUNT];
|
|
|
|
int arrow_index_from_direction(struct move dir) {
|
|
auto clamped = move_clamp_to_1(dir);
|
|
return (clamped.dy + 1) * 3 + (clamped.dx + 1);
|
|
}
|
|
|
|
struct pos pos_from_touchPosition(touchPosition touch_pos) {
|
|
return (struct pos){touch_pos.px, touch_pos.py};
|
|
}
|
|
|
|
struct pos hit_tile(struct pos screen_point) {
|
|
auto playfield_point = inner_coords(playfield, screen_point);
|
|
return tile_from_point(playfield_point);
|
|
}
|
|
|
|
struct pos tile_on_screen(struct pos tile) {
|
|
auto on_playfield = tile_top_left(tile);
|
|
return outer_coords(playfield, on_playfield);
|
|
}
|
|
|
|
void sprite_frame(u16 *tile_memory, int frame_num) {
|
|
constexpr int frame_size = 16 * 16;
|
|
memcpy(tile_memory, offset_ptr(spriteTiles, frame_num * frame_size), frame_size);
|
|
}
|
|
|
|
u16 *allocate_sprite_graphics(void) {
|
|
return oamAllocateGfx(&oamMain, SpriteSize_16x16, SpriteColorFormat_256Color);
|
|
}
|
|
|
|
u16 *unit_sprites[UNIT_TYPES_COUNT];
|
|
|
|
void load_unit_sprites(void) {
|
|
for (int i = 0; i < UNIT_TYPES_COUNT; i++) {
|
|
unit_sprites[i] = allocate_sprite_graphics();
|
|
sprite_frame(unit_sprites[i], unit_types[i].sprite);
|
|
}
|
|
}
|
|
|
|
void init_graphics_sub(void) {
|
|
videoSetModeSub(MODE_0_2D);
|
|
|
|
vramSetBankC(VRAM_C_SUB_BG);
|
|
|
|
subBg = bgInitHiddenSub(0, BgType_Text8bpp, BgSize_T_256x256, 0, 1);
|
|
|
|
memcpy(BG_PALETTE_SUB, backgroundPal, backgroundPalLen);
|
|
|
|
memcpy(bgGetGfxPtr(subBg), backgroundTiles, backgroundTilesLen);
|
|
}
|
|
|
|
constexpr int pen_width = UNIT_TYPES_COUNT;
|
|
constexpr int pen_height = 8;
|
|
|
|
void draw_pen(u16 *map, struct pos pos, enum players player) {
|
|
|
|
constexpr enum bg_tiles tiles_base[PLAYERS_COUNT] = {
|
|
[P1] = P1_PEN_TILES_START,
|
|
[P2] = P2_PEN_TILES_START,
|
|
};
|
|
enum bg_tiles b = tiles_base[player];
|
|
|
|
put_tile_at(map, (struct pos){pos.x, pos.y}, tiles[b + TOP_LEFT_CORNER]);
|
|
for (int dx = 0; dx < pen_width; dx++) {
|
|
put_tile_at(map, (struct pos){pos.x + 1 + dx, pos.y}, tiles[b + TOP_EDGE]);
|
|
}
|
|
put_tile_at(map, (struct pos){pos.x + 1 + pen_width, pos.y}, tiles[b + TOP_RIGHT_CORNER]);
|
|
|
|
for (int dy = 0; dy < pen_height; dy++) {
|
|
put_tile_at(map, (struct pos){pos.x, pos.y + 1 + dy}, tiles[b + LEFT_EDGE]);
|
|
for (int dx = 0; dx < pen_width; dx++) {
|
|
if (undeployed_units[player][dx] >= dy + 1) {
|
|
put_tile_at(map, (struct pos){pos.x + 1 + dx, pos.y + 1 + dy}, tiles[unit_types[dx].sub_tiles[player]]);
|
|
} else {
|
|
put_tile_at(map, (struct pos){pos.x + 1 + dx, pos.y + 1 + dy}, tiles[b + EMPTY]);
|
|
}
|
|
}
|
|
put_tile_at(map, (struct pos){pos.x + 1 + pen_width, pos.y + 1 + dy}, tiles[b + RIGHT_EDGE]);
|
|
}
|
|
|
|
put_tile_at(map, (struct pos){pos.x, pos.y + pen_height}, tiles[b + BOTTOM_LEFT_CORNER]);
|
|
for (int dx = 0; dx < pen_width; dx++) {
|
|
put_tile_at(map, (struct pos){pos.x + 1 + dx, pos.y + pen_height}, tiles[b + BOTTOM_EDGE]);
|
|
}
|
|
put_tile_at(map, (struct pos){pos.x + 1 + pen_width, pos.y + pen_height}, tiles[b + BOTTOM_RIGHT_CORNER]);
|
|
}
|
|
|
|
void update_bg_sub(void) {
|
|
u16 *map = bgGetMapPtr(subBg);
|
|
|
|
for (int y = 0; y < screen_height_chars / 2; y++) {
|
|
for (int x = 0; x < bg_width_chars / 2; x++) {
|
|
put_tile_at(map, (struct pos){x, y}, tiles[SUB_SCREEN_BG]);
|
|
}
|
|
}
|
|
|
|
put_tile_at(map, (struct pos){1, 10 - pen_height}, tiles[P1_LABEL_LEFT]);
|
|
put_tile_at(map, (struct pos){2, 10 - pen_height}, tiles[P1_LABEL_RIGHT]);
|
|
|
|
put_tile_at(map, (struct pos){14, 10 - pen_height}, tiles[P2_LABEL_LEFT]);
|
|
put_tile_at(map, (struct pos){15, 10 - pen_height}, tiles[P2_LABEL_RIGHT]);
|
|
|
|
put_tile_at(map, (struct pos){7, 1}, tiles[TITLE_1]);
|
|
put_tile_at(map, (struct pos){8, 1}, tiles[TITLE_2]);
|
|
put_tile_at(map, (struct pos){7, 2}, tiles[TITLE_3]);
|
|
put_tile_at(map, (struct pos){8, 2}, tiles[TITLE_4]);
|
|
|
|
draw_pen(map, (struct pos){0, 11 - pen_height}, P1);
|
|
draw_pen(map, (struct pos){14 - pen_width, 11 - pen_height}, P2);
|
|
|
|
if (left_to_deploy(P1) > 0) {
|
|
put_tile_at(map, (struct pos){1 + unit_to_place, 11}, tiles[P1_PEN_OPEN_BOTTOM]);
|
|
}
|
|
}
|
|
|
|
void init_graphics(void) {
|
|
lcdMainOnBottom();
|
|
|
|
videoSetMode(MODE_0_2D);
|
|
|
|
vramSetBankA(VRAM_A_MAIN_BG);
|
|
vramSetBankB(VRAM_B_MAIN_SPRITE);
|
|
|
|
mainBg = bgInitHidden(0, BgType_Text8bpp, BgSize_T_256x256, 0, 1);
|
|
|
|
memcpy(BG_PALETTE, backgroundPal, backgroundPalLen);
|
|
memcpy(bgGetGfxPtr(mainBg), backgroundTiles, backgroundTilesLen);
|
|
|
|
for (int i = 0; i < BG_TILES_COUNT; i++) {
|
|
int y = i / (bg_width_chars / 2);
|
|
int x = i % (bg_width_chars / 2);
|
|
int top_left_character = 2 * y * bg_width_chars + 2 * x;
|
|
tiles[i] = get_tile(backgroundMap + top_left_character);
|
|
}
|
|
|
|
oamInit(&oamMain, SpriteMapping_1D_32, false);
|
|
|
|
memcpy(SPRITE_PALETTE, spritePal, spritePalLen);
|
|
|
|
load_unit_sprites();
|
|
|
|
for (int i = 0; i < 9; i++) {
|
|
arrows[i] = allocate_sprite_graphics();
|
|
sprite_frame(arrows[i], ARROWS_START + i);
|
|
}
|
|
}
|
|
|
|
void update_bg(void) {
|
|
for (int y = 0; y < screen_height_chars; y += 2) {
|
|
u16 *line = bgGetMapPtr(mainBg) + y * bg_width_chars;
|
|
for (int x = 0; x < bg_width_chars; x += 2) {
|
|
if (x == 0 || y == 0 || x == bg_width_chars - 2 || y == screen_height_chars - 2) {
|
|
put_tile(line + x, tiles[BORDER]);
|
|
} else {
|
|
int grid_x = x / 2 - 1;
|
|
int grid_y = y / 2 - 1;
|
|
put_tile(line + x, tiles[grid[grid_y][grid_x]]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void set_sprite_attributes(int sprite_id, struct pos top_left, u16 *graphics, bool hidden) {
|
|
oamSet(&oamMain,
|
|
sprite_id,
|
|
top_left.x, top_left.y,
|
|
0, // Priority
|
|
0, // Palette index
|
|
SpriteSize_16x16, SpriteColorFormat_256Color,
|
|
graphics,
|
|
-1, false, // Affine info (not used)
|
|
hidden,
|
|
false, false, // H, V flip
|
|
false // Mosaic
|
|
);
|
|
}
|
|
|
|
void place_sprite(int sprite_id, struct pos top_left, u16 *graphics) {
|
|
set_sprite_attributes(sprite_id, top_left, graphics, false);
|
|
}
|
|
|
|
void hide_sprite(int sprite_id) {
|
|
set_sprite_attributes(sprite_id, (struct pos){}, NULL, true);
|
|
}
|
|
|
|
void play_effect_oneshot(mm_word effect, struct pos at) {
|
|
int panning = 256 * at.x / grid_rect.w;
|
|
mm_sfxhand h = mmEffect(effect);
|
|
mmEffectPanning(h, panning);
|
|
mmEffectVolume(h, 255);
|
|
mmEffectRate(h, 1024);
|
|
}
|
|
|
|
struct moving_unit {
|
|
struct pos on_screen;
|
|
struct move direction;
|
|
int dist_left;
|
|
int speed;
|
|
enum unit_types type;
|
|
enum players player;
|
|
};
|
|
|
|
constexpr int moving_units_max = 1;
|
|
struct moving_unit moving_units[moving_units_max];
|
|
|
|
void place_unit(struct pos tile, enum players player, enum unit_types type) {
|
|
grid[tile.y][tile.x] = unit_types[type].tiles[player];
|
|
}
|
|
|
|
bool is_unit_on_tile(enum bg_tiles tile, enum unit_types type) {
|
|
for (int player = 0; player < PLAYERS_COUNT; player++) {
|
|
if (tile == unit_types[type].tiles[player]) { return true; }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool is_tile_unoccupied(enum bg_tiles tile) {
|
|
return tile == P1_PLAYFIELD || tile == P2_PLAYFIELD || tile == PLAYFIELD;
|
|
}
|
|
|
|
void unit_has_arrived(struct pos to, enum unit_types type, enum players player) {
|
|
struct pos tile = hit_tile(to);
|
|
|
|
if (!is_tile_unoccupied(grid[tile.y][tile.x])) {
|
|
for (int i = 0; i < UNIT_TYPES_COUNT; i++) {
|
|
if (is_unit_on_tile(grid[tile.y][tile.x], i)) {
|
|
undeployed_units[player][i]++;
|
|
}
|
|
}
|
|
}
|
|
place_unit(tile, player, type);
|
|
}
|
|
|
|
int animate_moving_units(void) {
|
|
int units_still_moving = 0;
|
|
|
|
for (int i = 0; i < moving_units_max; i++) {
|
|
auto p = &moving_units[i];
|
|
if (p->dist_left > 0) {
|
|
units_still_moving++;
|
|
int to_move_this_frame = p->speed;
|
|
while (to_move_this_frame > 0 && p->dist_left > 0) {
|
|
p->on_screen = offset_pos(p->on_screen, p->direction);
|
|
p->dist_left--;
|
|
to_move_this_frame--;
|
|
}
|
|
place_sprite(1, p->on_screen, unit_sprites[p->type]);
|
|
|
|
if (p->dist_left == 0) {
|
|
units_still_moving--;
|
|
unit_has_arrived(p->on_screen, p->type, p->player);
|
|
hide_sprite(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return units_still_moving;
|
|
}
|
|
|
|
void move_unit(struct pos from, struct pos to, enum unit_types type, enum players player) {
|
|
if (grid[from.y][from.x] == unit_types[type].tiles[P1]) {
|
|
grid[from.y][from.x] = P1_PLAYFIELD;
|
|
} else if (grid[from.y][from.x] == unit_types[type].tiles[P2]) {
|
|
grid[from.y][from.x] = P2_PLAYFIELD;
|
|
} else {
|
|
grid[from.y][from.x] = PLAYFIELD;
|
|
}
|
|
|
|
for (int i = 0; i < moving_units_max; i++) {
|
|
auto p = &moving_units[i];
|
|
|
|
if (p->dist_left == 0) {
|
|
play_effect_oneshot(SFX_WALK, from);
|
|
p->on_screen = tile_on_screen(from);
|
|
p->direction = move_clamp_to_1(pos_difference(to, from));
|
|
p->dist_left = tile_side;
|
|
p->speed = 1;
|
|
p->type = type;
|
|
p->player = player;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// No free spot on the list, just teleport it there
|
|
unit_has_arrived(tile_on_screen(to), type, player);
|
|
}
|
|
|
|
bool is_move_allowed(struct pos from_tile, struct pos towards_tile, enum players player) {
|
|
auto drag_direction = move_clamp_to_1(pos_difference(towards_tile, from_tile));
|
|
auto target_tile = offset_pos(from_tile, drag_direction);
|
|
enum bg_tiles *from = &grid[from_tile.y][from_tile.x];
|
|
enum bg_tiles *to = &grid[target_tile.y][target_tile.x];
|
|
|
|
if (
|
|
*from == unit_types[UNIT_WALKER].tiles[player] &&
|
|
(is_tile_unoccupied(*to) || *to == GRASS_ON_PLAYFIELD)
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool do_player_move(struct pos drag_from_tile, struct pos drag_to_tile, enum players player) {
|
|
auto drag_direction = move_clamp_to_1(pos_difference(drag_to_tile, drag_from_tile));
|
|
auto target_tile = offset_pos(drag_from_tile, drag_direction);
|
|
|
|
if (is_move_allowed(drag_from_tile, drag_to_tile, player)) {
|
|
for (int i = 0; i < UNIT_TYPES_COUNT; i++) {
|
|
if (grid[drag_from_tile.y][drag_from_tile.x] == unit_types[i].tiles[player]) {
|
|
move_unit(drag_from_tile, target_tile, i, player);
|
|
}
|
|
}
|
|
return true;
|
|
} else {
|
|
play_effect_oneshot(SFX_KNOCK, target_tile);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const char *player_names[PLAYERS_COUNT] = {
|
|
[P1] = "Player",
|
|
[P2] = "CPU",
|
|
};
|
|
|
|
const char *unit_names[UNIT_TYPES_COUNT] = {
|
|
[UNIT_GRASS] = "Grass",
|
|
[UNIT_WALKER] = "Walker",
|
|
};
|
|
|
|
void deploy_unit(struct pos tile, enum players player, enum unit_types type) {
|
|
assert(is_tile_unoccupied(grid[tile.y][tile.x]));
|
|
assert(undeployed_units[player][type] > 0);
|
|
play_effect_oneshot(SFX_DRAG, tile);
|
|
place_unit(tile, player, type);
|
|
undeployed_units[player][type]--;
|
|
}
|
|
|
|
void undeploy_unit(struct pos tile, enum players player, enum unit_types type) {
|
|
assert(grid[tile.y][tile.x] == unit_types[type].tiles[player]);
|
|
undeployed_units[player][type]++;
|
|
grid[tile.y][tile.x] = PLAYFIELD;
|
|
}
|
|
|
|
constexpr struct move directions[8] = {
|
|
{-1, -1}, {0, -1}, {1, -1},
|
|
{-1, 0}, {1, 0},
|
|
{-1, 1}, {0, 1}, {1, 1},
|
|
};
|
|
|
|
struct maybe_pos find_tile(bool (*predicate)(enum bg_tiles), struct pos start) {
|
|
for (int dy = 0; dy < grid_rect.h; dy++) {
|
|
int y = (start.y + dy) % grid_rect.h;
|
|
for (int dx = 0; dx < grid_rect.w; dx++) {
|
|
int x = (start.x + dx) % grid_rect.w;
|
|
if (predicate(grid[y][x])) {
|
|
return (struct maybe_pos){IS, {x, y}};
|
|
}
|
|
}
|
|
}
|
|
|
|
return (struct maybe_pos){ISNAE};
|
|
}
|
|
|
|
void for_tile(bool (*func)(struct pos), struct pos start) {
|
|
for (int dy = 0; dy < grid_rect.h; dy++) {
|
|
int y = (start.y + dy) % grid_rect.h;
|
|
for (int dx = 0; dx < grid_rect.w; dx++) {
|
|
int x = (start.x + dx) % grid_rect.w;
|
|
if (func((struct pos){x, y})) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool is_tile_player_walker(enum bg_tiles tile) {
|
|
return tile == unit_types[UNIT_WALKER].tiles[P1];
|
|
}
|
|
|
|
bool is_tile_player_playfield(enum bg_tiles tile) {
|
|
return tile == P1_PLAYFIELD;
|
|
}
|
|
|
|
struct pos random_grid_square(void) {
|
|
return (struct pos){rand() % grid_rect.w, rand() % grid_rect.h};
|
|
}
|
|
|
|
bool move_ai_unit(struct pos from) {
|
|
for (int type = 0; type < UNIT_TYPES_COUNT; type++) {
|
|
if (grid[from.y][from.x] == unit_types[type].tiles[P2]) {
|
|
struct maybe_pos target = {.tag = ISNAE};
|
|
|
|
if (target.tag == ISNAE) {
|
|
target = find_tile(is_tile_player_walker, random_grid_square());
|
|
}
|
|
|
|
if (target.tag == ISNAE) {
|
|
target = find_tile(is_tile_player_playfield, random_grid_square());
|
|
}
|
|
|
|
if (target.tag == IS) {
|
|
auto dir = move_clamp_to_1(pos_difference(target.payload, from));
|
|
auto to = offset_pos(from, dir);
|
|
if (!is_move_allowed(from, to, P2)) continue;
|
|
move_unit(from, to, type, P2);
|
|
return true;
|
|
}
|
|
|
|
int dir_start = rand() % 8;
|
|
for (int ddir = 0; ddir < 8; ddir++) {
|
|
int dir = (dir_start + ddir) % 8;
|
|
auto to = offset_pos(from, directions[dir]);
|
|
if (!is_within(grid_rect, to)) continue;
|
|
if (!is_move_allowed(from, to, P2)) continue;
|
|
move_unit(from, to, type, P2);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ai_turn(void) {
|
|
if (left_to_deploy(P2) > 0) {
|
|
int type;
|
|
struct maybe_pos maybe_tile;
|
|
do {
|
|
type = rand() % UNIT_TYPES_COUNT;
|
|
maybe_tile = find_tile(is_tile_unoccupied, random_grid_square());
|
|
assert(maybe_tile.tag == IS);
|
|
} while(!can_player_deploy(maybe_tile.payload, P2, type));
|
|
|
|
deploy_unit(maybe_tile.payload, P2, type);
|
|
} else {
|
|
for_tile(move_ai_unit, random_grid_square());
|
|
}
|
|
}
|
|
|
|
#define ERRX(...) { printf(__VA_ARGS__); for (;;) { swiWaitForVBlank(); } }
|
|
|
|
#define LOAD_EFFECT(effect) { \
|
|
ret = mmLoadEffect(effect); \
|
|
if (ret != 0) { \
|
|
ERRX("mmLoadEffect(" #effect "): %d\n", ret); \
|
|
} \
|
|
}
|
|
|
|
void autoselect_unit_to_deploy(void) {
|
|
if (undeployed_units[P1][unit_to_place] > 0) {
|
|
return;
|
|
}
|
|
|
|
if (left_to_deploy(P1) > 0) {
|
|
for (int d = 0; d < UNIT_TYPES_COUNT; d++) {
|
|
int i = (unit_to_place + d) % UNIT_TYPES_COUNT;
|
|
if (undeployed_units[P1][i] > 0) {
|
|
unit_to_place = i;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void for_neighbours(void* ctx, void (*each)(void*, struct pos), struct pos pos) {
|
|
for (int y = 0; y < grid_rect.h; y++) {
|
|
for (int x = 0; x < grid_rect.w; x++) {
|
|
for (int i = 0; i < 8; i++) {
|
|
auto dir = directions[i];
|
|
auto neighbour = offset_pos(pos, dir);
|
|
if (is_within(grid_rect, neighbour)) {
|
|
each(ctx, neighbour);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct destroy_adjacent_walkers_ctx {
|
|
enum players player;
|
|
bool any_found;
|
|
};
|
|
|
|
void destroy_adjacent_walkers(void *ctx, struct pos pos) {
|
|
struct destroy_adjacent_walkers_ctx *p = ctx;
|
|
if (grid[pos.y][pos.x] == unit_types[UNIT_WALKER].tiles[p->player]) {
|
|
play_effect_oneshot(SFX_AA, pos);
|
|
undeploy_unit(pos, p->player, UNIT_WALKER);
|
|
p->any_found = true;
|
|
}
|
|
}
|
|
|
|
void cellular_automata_walker(struct pos pos) {
|
|
for (int player = 0; player < PLAYERS_COUNT; player++) {
|
|
if (grid[pos.y][pos.x] == unit_types[UNIT_WALKER].tiles[player]) {
|
|
struct destroy_adjacent_walkers_ctx destroy_adjacent_walkers_ctx = {
|
|
.player = P2 - player,
|
|
.any_found = false,
|
|
};
|
|
for_neighbours(&destroy_adjacent_walkers_ctx, destroy_adjacent_walkers, pos);
|
|
|
|
if (destroy_adjacent_walkers_ctx.any_found) {
|
|
undeploy_unit(pos, player, UNIT_WALKER);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void clear_colour_around(void *, struct pos pos) {
|
|
if (is_tile_unoccupied(grid[pos.y][pos.x])) {
|
|
grid[pos.y][pos.x] = PLAYFIELD;
|
|
}
|
|
}
|
|
|
|
void cellular_automata(void) {
|
|
for (int y = 0; y < grid_rect.h; y++) {
|
|
for (int x = 0; x < grid_rect.w; x++) {
|
|
struct pos pos = {x, y};
|
|
cellular_automata_walker(pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
void initial_grid(void) {
|
|
for (int y = 0; y < grid_rect.h; y++) {
|
|
for (int x = 0; x < grid_rect.w; x++) {
|
|
if (x < grid_rect.w / 2) {
|
|
grid[y][x] = P1_PLAYFIELD;
|
|
} else {
|
|
grid[y][x] = P2_PLAYFIELD;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(void) {
|
|
init_graphics();
|
|
|
|
initial_grid();
|
|
|
|
update_bg();
|
|
|
|
bgShow(mainBg);
|
|
|
|
soundEnable();
|
|
|
|
int ret;
|
|
|
|
consoleDemoInit();
|
|
|
|
if (!mmInitDefaultMem((mm_addr)soundbank_bin)) {
|
|
ERRX("mmInitDefaultMem() failed\n");
|
|
}
|
|
|
|
LOAD_EFFECT(SFX_DRAG);
|
|
LOAD_EFFECT(SFX_DRAG_START);
|
|
LOAD_EFFECT(SFX_KNOCK);
|
|
LOAD_EFFECT(SFX_WALK);
|
|
LOAD_EFFECT(SFX_AA);
|
|
|
|
init_graphics_sub();
|
|
|
|
update_bg_sub();
|
|
|
|
bgShow(subBg);
|
|
|
|
constexpr int drag_direction_arrow_sprite = 0;
|
|
|
|
struct pos drag_from_tile = {}, drag_to_tile = {};
|
|
bool dragging = false;
|
|
|
|
bool ready_to_move = true, player_want_move = false, ai_want_move = false;
|
|
|
|
bool player_did_turn = false, must_run_cellular_automata = false;
|
|
|
|
autoselect_unit_to_deploy();
|
|
|
|
// Sorry, this is a giant hack
|
|
while (left_to_deploy(P2)) {
|
|
// Relies on the AI always placing first
|
|
ai_turn();
|
|
}
|
|
|
|
for (;;) {
|
|
if (must_run_cellular_automata && ready_to_move) {
|
|
must_run_cellular_automata = false;
|
|
cellular_automata();
|
|
}
|
|
|
|
swiWaitForVBlank();
|
|
|
|
update_bg();
|
|
update_bg_sub();
|
|
|
|
oamUpdate(&oamMain);
|
|
|
|
scanKeys();
|
|
|
|
unsigned keys_held = keysHeld();
|
|
unsigned keys_down = keysDown();
|
|
|
|
if (keys_held & KEY_TOUCH && !player_want_move) {
|
|
touchPosition touch_pos;
|
|
touchRead(&touch_pos);
|
|
auto touched = pos_from_touchPosition(touch_pos);
|
|
|
|
if (is_within(playfield, touched)) {
|
|
if (keys_down & KEY_TOUCH) {
|
|
drag_from_tile = hit_tile(touched);
|
|
dragging = true;
|
|
}
|
|
|
|
drag_to_tile = hit_tile(touched);
|
|
}
|
|
}
|
|
|
|
if (dragging && ready_to_move) {
|
|
if (can_player_deploy(drag_from_tile, P1, unit_to_place)) {
|
|
player_did_turn = true;
|
|
deploy_unit(drag_from_tile, P1, unit_to_place);
|
|
}
|
|
}
|
|
|
|
if (!(keys_held & KEY_TOUCH) && dragging) {
|
|
dragging = false;
|
|
player_want_move = true;
|
|
}
|
|
|
|
if (keys_down & KEY_LEFT) {
|
|
if (unit_to_place == 0) { unit_to_place = UNIT_TYPES_COUNT; }
|
|
unit_to_place--;
|
|
}
|
|
|
|
if (keys_down & KEY_RIGHT) {
|
|
unit_to_place++;
|
|
if (unit_to_place == UNIT_TYPES_COUNT) { unit_to_place = 0; }
|
|
}
|
|
|
|
if (keys_down & KEY_SELECT) {
|
|
credits();
|
|
init_graphics_sub();
|
|
update_bg_sub();
|
|
bgShow(subBg);
|
|
}
|
|
|
|
if (ai_want_move && ready_to_move) {
|
|
ai_want_move = false;
|
|
ai_turn();
|
|
must_run_cellular_automata = true;
|
|
}
|
|
|
|
if (player_want_move && ready_to_move) {
|
|
player_want_move = false;
|
|
player_did_turn |= do_player_move(drag_from_tile, drag_to_tile, P1);
|
|
|
|
if (player_did_turn) {
|
|
player_did_turn = false;
|
|
ai_want_move = true;
|
|
must_run_cellular_automata = true;
|
|
}
|
|
}
|
|
|
|
if (dragging && !is_same_pos(drag_from_tile, drag_to_tile) && is_move_allowed(drag_from_tile, drag_to_tile, P1)) {
|
|
auto drag_direction = move_clamp_to_1(pos_difference(drag_to_tile, drag_from_tile));
|
|
int arrow_index = arrow_index_from_direction(drag_direction);
|
|
auto half_tile = scale_move(drag_direction, tile_side, 2);
|
|
auto arrow_top_left = offset_pos(tile_on_screen(drag_from_tile), half_tile);
|
|
place_sprite(drag_direction_arrow_sprite, arrow_top_left, arrows[arrow_index]);
|
|
} else {
|
|
hide_sprite(drag_direction_arrow_sprite);
|
|
}
|
|
|
|
int units_still_moving = animate_moving_units();
|
|
ready_to_move = units_still_moving == 0;
|
|
}
|
|
}
|