From 7a6d042da73f8cf6ff631c6a22921cfc48953115 Mon Sep 17 00:00:00 2001 From: rgimad <33692565+rgimad@users.noreply.github.com> Date: Sat, 11 Jan 2025 15:07:11 +0300 Subject: [PATCH] upload dino game, add to img, to game center --- data/Tupfile.lua | 1 + data/common/icons32.png | Bin 41268 -> 45099 bytes data/common/settings/games.ini | 1 + programs/games/dino/Makefile | 18 ++ programs/games/dino/Tupfile.lua | 25 ++ programs/games/dino/cloud.c | 34 +++ programs/games/dino/cloud.h | 29 +++ programs/games/dino/collisionbox.h | 11 + programs/games/dino/config.h | 19 ++ programs/games/dino/distance_meter.c | 134 +++++++++++ programs/games/dino/distance_meter.h | 44 ++++ programs/games/dino/game_over_panel.c | 22 ++ programs/games/dino/game_over_panel.h | 24 ++ programs/games/dino/graphics.c | 115 +++++++++ programs/games/dino/graphics.h | 41 ++++ programs/games/dino/horizon.c | 147 ++++++++++++ programs/games/dino/horizon.h | 41 ++++ programs/games/dino/horizon_line.c | 56 +++++ programs/games/dino/horizon_line.h | 33 +++ programs/games/dino/main.c | 133 ++++++++++ programs/games/dino/misc.c | 28 +++ programs/games/dino/misc.h | 13 + programs/games/dino/obstacle.c | 156 ++++++++++++ programs/games/dino/obstacle.h | 67 ++++++ programs/games/dino/runner.c | 333 ++++++++++++++++++++++++++ programs/games/dino/runner.h | 91 +++++++ programs/games/dino/sprites.h | 169 +++++++++++++ programs/games/dino/trex.c | 219 +++++++++++++++++ programs/games/dino/trex.h | 83 +++++++ programs/games/dino/ulist.c | 241 +++++++++++++++++++ programs/games/dino/ulist.h | 31 +++ 31 files changed, 2359 insertions(+) create mode 100644 programs/games/dino/Makefile create mode 100644 programs/games/dino/Tupfile.lua create mode 100644 programs/games/dino/cloud.c create mode 100644 programs/games/dino/cloud.h create mode 100644 programs/games/dino/collisionbox.h create mode 100644 programs/games/dino/config.h create mode 100644 programs/games/dino/distance_meter.c create mode 100644 programs/games/dino/distance_meter.h create mode 100644 programs/games/dino/game_over_panel.c create mode 100644 programs/games/dino/game_over_panel.h create mode 100644 programs/games/dino/graphics.c create mode 100644 programs/games/dino/graphics.h create mode 100644 programs/games/dino/horizon.c create mode 100644 programs/games/dino/horizon.h create mode 100644 programs/games/dino/horizon_line.c create mode 100644 programs/games/dino/horizon_line.h create mode 100644 programs/games/dino/main.c create mode 100644 programs/games/dino/misc.c create mode 100644 programs/games/dino/misc.h create mode 100644 programs/games/dino/obstacle.c create mode 100644 programs/games/dino/obstacle.h create mode 100644 programs/games/dino/runner.c create mode 100644 programs/games/dino/runner.h create mode 100644 programs/games/dino/sprites.h create mode 100644 programs/games/dino/trex.c create mode 100644 programs/games/dino/trex.h create mode 100644 programs/games/dino/ulist.c create mode 100644 programs/games/dino/ulist.h diff --git a/data/Tupfile.lua b/data/Tupfile.lua index a0c324cb2..ca7792abe 100644 --- a/data/Tupfile.lua +++ b/data/Tupfile.lua @@ -39,6 +39,7 @@ img_files = { {"3D/HOUSE.3DS", "common/3d/house.3ds"}, {"File Managers/ICONS.INI", "common/File Managers/icons.ini"}, {"GAMES/FLPYBIRD", SRC_PROGS .. "/games/flappybird/Release/flappybird"}, + {"GAMES/DINO", SRC_PROGS .. "/games/dino/dino"}, {"FONTS/TAHOMA.KF", "common/fonts/tahoma.kf"}, -- {"LIB/ICONV.OBJ", "common/lib/iconv.obj"}, {"LIB/KMENU.OBJ", "common/lib/kmenu.obj"}, diff --git a/data/common/icons32.png b/data/common/icons32.png index d7d28a10f6df41c1c633ae3a40980e13a31bc628..c30931ef408345ce87b78806ad6654f302dff3a4 100644 GIT binary patch literal 45099 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 literal 41268 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 diff --git a/data/common/settings/games.ini b/data/common/settings/games.ini index 653eb1d86..3b60b7e3d 100644 --- a/data/common/settings/games.ini +++ b/data/common/settings/games.ini @@ -16,6 +16,7 @@ Freecell=games/freecell,68 Pong=games/pong,101 Pong3=games/pong3,12 Arcanii=/kg/arcanii,12 +Dino=games/dino,129 [Arcade] LaserTank=/kg/lasertank/lasertank,72 diff --git a/programs/games/dino/Makefile b/programs/games/dino/Makefile new file mode 100644 index 000000000..04f7eb62a --- /dev/null +++ b/programs/games/dino/Makefile @@ -0,0 +1,18 @@ +KTCC_DIR = ../kolibrios-nextgen/programs/develop/ktcc/trunk +KLIBC = $(KTCC_DIR)/libc.obj + +NAME = dino + +KTCC = $(KTCC_DIR)/bin/kos32-tcc +KPACK = kpack + +SRC = $(wildcard *.c) +FLAGS= -B$(KTCC_DIR)/bin -I $(KLIBC)/include -Wall -stack=20480 +LIBS = -limg + +all: $(SRC) + $(KTCC) $(FLAGS) $(SRC) $(LIBS) -o $(NAME) +# $(KPACK) $(NAME) + +clean: + rm $(NAME) diff --git a/programs/games/dino/Tupfile.lua b/programs/games/dino/Tupfile.lua new file mode 100644 index 000000000..89ced5bd7 --- /dev/null +++ b/programs/games/dino/Tupfile.lua @@ -0,0 +1,25 @@ +if tup.getconfig("NO_TCC") ~= "" then return end +if tup.getconfig("HELPERDIR") == "" +then + HELPERDIR = "../../../programs" +end +tup.include(HELPERDIR .. "/use_tcc.lua") + +SRCS = { + "cloud.c" + "game_over_panel.c" + "horizon.c" + "main.c" + "obstacle.c" + "trex.c + "distance_meter.c" + "graphics.c" + "horizon_line.c" + "misc.c" + "runner.c" + "ulist.c +} + +LIBS = " -limg" + +link_tcc(SRCS, "dino"); diff --git a/programs/games/dino/cloud.c b/programs/games/dino/cloud.c new file mode 100644 index 000000000..e695ec551 --- /dev/null +++ b/programs/games/dino/cloud.c @@ -0,0 +1,34 @@ +#include "cloud.h" + +void cloudInit(Cloud* cloud, int w) { + cloud->width = w; + cloud->xPos = w; + cloud->yPos = 0; + cloud->remove = false; + cloud->cloudGap = getRandomNumber(CLOUD_MIN_GAP, CLOUD_MAX_GAP); + + cloud->yPos = getRandomNumber(CLOUD_MAX_SKY_LEVEL, CLOUD_MIN_SKY_LEVEL); // NOTE why swapped + cloudDraw(cloud); +} + +void cloudDraw(const Cloud* cloud) { + graphicsBlitAtlasImage(ATLAS_CLOUD_X, ATLAS_CLOUD_Y, cloud->xPos, cloud->yPos, CLOUD_WIDTH, CLOUD_HEIGHT, false); +} + +void cloudUpdate(Cloud* cloud, double speed) { + // printf("cloudUpdate(., %f)\n", speed); + if (!cloud->remove) { + cloud->xPos -= (int)ceil(speed); + cloudDraw(cloud); + + // Mark as removeable if no longer in the canvas. + if (!cloudIsVisible(cloud)) { + cloud->remove = true; + } + } +} + +bool cloudIsVisible(const Cloud* cloud) { + return cloud->xPos + CLOUD_WIDTH > 0; +} + diff --git a/programs/games/dino/cloud.h b/programs/games/dino/cloud.h new file mode 100644 index 000000000..604653a2f --- /dev/null +++ b/programs/games/dino/cloud.h @@ -0,0 +1,29 @@ +#ifndef CLOUD_H +#define CLOUD_H + +#include +#include +#include "graphics.h" +#include "misc.h" + +#define CLOUD_WIDTH 46 +#define CLOUD_HEIGHT 14 +#define CLOUD_MAX_GAP 400 +#define CLOUD_MAX_SKY_LEVEL 30 +#define CLOUD_MIN_GAP 100 +#define CLOUD_MIN_SKY_LEVEL 71 + +typedef struct { + int width; + int xPos; + int yPos; + bool remove; + int cloudGap; +} Cloud; + +void cloudInit(Cloud* cloud, int w); +void cloudDraw(const Cloud* cloud); +void cloudUpdate(Cloud* cloud, double speed); +bool cloudIsVisible(const Cloud* cloud); + +#endif diff --git a/programs/games/dino/collisionbox.h b/programs/games/dino/collisionbox.h new file mode 100644 index 000000000..e9e74ab8c --- /dev/null +++ b/programs/games/dino/collisionbox.h @@ -0,0 +1,11 @@ +#ifndef COLLISIONBOX_H +#define COLLISIONBOX_H + +typedef struct { + int x; + int y; + int width; + int height; +} CollisionBox; + +#endif diff --git a/programs/games/dino/config.h b/programs/games/dino/config.h new file mode 100644 index 000000000..2b6feea82 --- /dev/null +++ b/programs/games/dino/config.h @@ -0,0 +1,19 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#define DEFAULT_WIDTH 600 +#define FPS 60 + +#define DELTA_MS_DEFAULT 20 + +#define WINDOW_TITLE "DINO. Jump: UP/SPACE Duck: DOWN Restart: jump or ENTER" + +// #define DBG + +#ifdef DBG +#define dbg_printf(...) debug_printf(__VA_ARGS__) +#else +#define dbg_printf(...) +#endif + +#endif diff --git a/programs/games/dino/distance_meter.c b/programs/games/dino/distance_meter.c new file mode 100644 index 000000000..e69c9da53 --- /dev/null +++ b/programs/games/dino/distance_meter.c @@ -0,0 +1,134 @@ +#include "distance_meter.h" + +DistanceMeter distanceMeter; + +void distanceMeterInit(int w) { + distanceMeter.x = 0; + distanceMeter.y = 5; + distanceMeter.currentDistance = 0; + distanceMeter.maxScore = 0; + distanceMeter.achievement = false; + distanceMeter.flashTimer = 0; + distanceMeter.flashIterations = 0; + distanceMeter.invertTrigger = false; + distanceMeter.maxScoreUnits = DM_MAX_DISTANCE_UNITS; + distanceMeterCalcXPos(w); + for (int i = 0; i < distanceMeter.maxScoreUnits; i++) { + distanceMeterDraw(i, 0, false); + } + distanceMeter.maxScore = (int)pow(10, distanceMeter.maxScoreUnits) - 1; + distanceMeter.digits[0] = '\0'; + distanceMeter.highScore[0] = '\0'; +} + +void distanceMeterCalcXPos(int w) { + distanceMeter.x = w - (DM_DEST_WIDTH * (distanceMeter.maxScoreUnits + 1)); +} + +void distanceMeterDraw(int digitPos, int value, bool opt_highscore) { + + int dx, dy; + if (opt_highscore) { + dx = distanceMeter.x - (distanceMeter.maxScoreUnits * 2) * DM_WIDTH; + dy = distanceMeter.y; + } + else { + dx = distanceMeter.x; + dy = distanceMeter.y; + } + //printf("%d %d %d %d %d %d\n", digitPos, value, opt_highscore, dx, dy, DM_WIDTH * value + ATLAS_TEXT_SPRITE_X); + graphicsBlitAtlasImage( + DM_WIDTH * value + ATLAS_TEXT_SPRITE_X, + 0 + ATLAS_TEXT_SPRITE_Y, + digitPos * DM_DEST_WIDTH + dx, + distanceMeter.y + dy, + DM_WIDTH, + DM_HEIGHT, + false + ); +} + +void distanceMeterDrawHighScore() { + // TODO canvasCtx.globalAlpha = .8; + for (int i = (int)strlen(distanceMeter.highScore) - 1; i >= 0; i--) { + distanceMeterDraw(i, distanceMeter.highScore[i] > 12 ? distanceMeter.highScore[i] - '0' : distanceMeter.highScore[i], true); + } +} + +void distanceMeterSetHighScore(int _distance) { + int distance = distanceMeterGetActualDistance(_distance); + distanceMeter.highScore[0] = 10; + distanceMeter.highScore[1] = 11; + distanceMeter.highScore[2] = 12; + intToStr(distance, distanceMeter.maxScoreUnits, distanceMeter.highScore + 3); +} + +void distanceMeterReset() { + distanceMeterUpdate(0, 0); + distanceMeter.achievement = false; +} + +int distanceMeterGetActualDistance(int distance) { + return distance ? (int)round(distance * DM_COEFFICIENT) : 0; +} + +bool distanceMeterUpdate(int deltaTime, int _distance) { + bool paint = true; + bool playSound = false; + int distance = _distance; + if (!distanceMeter.achievement) { + distance = distanceMeterGetActualDistance(distance); + // check if score has gone beyond the initial digit count. + if (distance > distanceMeter.maxScore && distanceMeter.maxScoreUnits == DM_MAX_DISTANCE_UNITS) { + distanceMeter.maxScoreUnits++; + distanceMeter.maxScore = distanceMeter.maxScore * 10 + 9; + } + // else { + // NOTE this.distance was in original but i didsnt see any usage of this field + // } + + if (distance > 0) { + // Acheivement unlocked + if (distance % DM_ACHIEVEMENT_DISTANCE == 0) { + // Flash score and play sound + distanceMeter.achievement = true; + distanceMeter.flashTimer = 0; + playSound = true; + } + // Create a string representation of the distance with leading 0. + intToStr(distance, distanceMeter.maxScoreUnits, distanceMeter.digits); + } + else { + intToStr(0, distanceMeter.maxScoreUnits, distanceMeter.digits); + } + } + else { + // Control flashing of the score on reaching acheivement. + if (distanceMeter.flashIterations <= DM_FLASH_ITERATIONS) { + distanceMeter.flashTimer += deltaTime; + + if (distanceMeter.flashTimer < DM_FLASH_DURATION) { + paint = false; + } + else if (distanceMeter.flashTimer > DM_FLASH_DURATION * 2) { + distanceMeter.flashTimer = 0; + distanceMeter.flashIterations++; + } + } + else { + distanceMeter.achievement = false; + distanceMeter.flashIterations = 0; + distanceMeter.flashTimer = 0; + } + } + + // Draw the digits if not flashing + if (paint) { + for (int i = distanceMeter.maxScoreUnits - 1; i >= 0; i--) { + distanceMeterDraw(i, (int)distanceMeter.digits[i] - '0', false); + } + } + + distanceMeterDrawHighScore(); + return playSound; +} diff --git a/programs/games/dino/distance_meter.h b/programs/games/dino/distance_meter.h new file mode 100644 index 000000000..2efa11be7 --- /dev/null +++ b/programs/games/dino/distance_meter.h @@ -0,0 +1,44 @@ +#ifndef DISTANCE_METER_H +#define DISTANCE_METER_H + +#include +#include +#include +#include "graphics.h" +#include "misc.h" + +#define DM_MAX_DISTANCE_UNITS 5 +#define DM_ACHIEVEMENT_DISTANCE 100 +#define DM_COEFFICIENT 0.025 +#define DM_FLASH_DURATION 1000/4 +#define DM_FLASH_ITERATIONS 3 +#define DM_WIDTH 10 +#define DM_HEIGHT 13 +#define DM_DEST_WIDTH 11 + +typedef struct { + int x; + int y; + int currentDistance; + int maxScore; + char digits[16]; + char highScore[16]; + bool achievement; + int flashTimer; + int flashIterations; + bool invertTrigger; + int maxScoreUnits; +} DistanceMeter; + +extern DistanceMeter distanceMeter; + +void distanceMeterInit(int w); +void distanceMeterCalcXPos(int w); +void distanceMeterDraw(int digitPos, int value, bool opt_highscore); +int distanceMeterGetActualDistance(int distance); +bool distanceMeterUpdate(int deltaTime, int _distance); +void distanceMeterDrawHighScore(); +void distanceMeterSetHighScore(int _distance); +void distanceMeterReset(); + +#endif diff --git a/programs/games/dino/game_over_panel.c b/programs/games/dino/game_over_panel.c new file mode 100644 index 000000000..ba9fefa05 --- /dev/null +++ b/programs/games/dino/game_over_panel.c @@ -0,0 +1,22 @@ +#include "game_over_panel.h" + +GameOverPanel gameOverPanel; + +void gameOverPanelInit(int width, int height) { + gameOverPanel.width = width; + gameOverPanel.height = height; +} + +void gameOverPanelDraw() { + double centerX = gameOverPanel.width / 2; + int textTargetX = (int)round(centerX - (GOP_TEXT_WIDTH / 2)); + int textTargetY = (int)round((gameOverPanel.height - 25) / 3); + int restartTargetX = centerX - (GOP_RESTART_WIDTH / 2); + int restartTargetY = gameOverPanel.height / 2; + // Game over text from sprite + graphicsBlitAtlasImage(GOP_TEXT_X + ATLAS_TEXT_SPRITE_X, GOP_TEXT_Y + ATLAS_TEXT_SPRITE_Y, + textTargetX, textTargetY, GOP_TEXT_WIDTH, GOP_TEXT_HEIGHT, false); + // Restart button + graphicsBlitAtlasImage(ATLAS_RESTART_X, ATLAS_RESTART_Y, + restartTargetX, restartTargetY, GOP_RESTART_WIDTH, GOP_RESTART_HEIGHT, false); +} diff --git a/programs/games/dino/game_over_panel.h b/programs/games/dino/game_over_panel.h new file mode 100644 index 000000000..864448283 --- /dev/null +++ b/programs/games/dino/game_over_panel.h @@ -0,0 +1,24 @@ +#ifndef GAME_OVER_PANEL_H +#define GAME_OVER_PANEL_H + +#include +#include "graphics.h" + +#define GOP_TEXT_X 0 +#define GOP_TEXT_Y 13 +#define GOP_TEXT_WIDTH 191 +#define GOP_TEXT_HEIGHT 11 +#define GOP_RESTART_WIDTH 36 +#define GOP_RESTART_HEIGHT 32 + +typedef struct { + int width; + int height; +} GameOverPanel; + +extern GameOverPanel gameOverPanel; + +void gameOverPanelInit(int width, int height); +void gameOverPanelDraw(); + +#endif diff --git a/programs/games/dino/graphics.c b/programs/games/dino/graphics.c new file mode 100644 index 000000000..445bbac81 --- /dev/null +++ b/programs/games/dino/graphics.c @@ -0,0 +1,115 @@ +#include "graphics.h" +#include "sprites.h" +#include + +/*static*/ ksys_colors_table_t sys_color_table; +/*static*/ ksys_pos_t win_pos; +/*static*/ Image* screenImage; +/*static*/ Image* spriteAtlas; + + +void graphicsInit() { + win_pos = _ksys_get_mouse_pos(KSYS_MOUSE_SCREEN_POS); + _ksys_get_system_colors(&sys_color_table); + spriteAtlas = img_decode((void*)sprites100, sizeof(sprites100), 0); + *((uint8_t*)spriteAtlas->Palette + 3) = 0; // set black as transparent + // for (int i = 0; i < 16; i++) { + // dbg_printf("%x\n", *((uint8_t*)spriteAtlas->Palette + i)); + // } + if (spriteAtlas->Type != IMAGE_BPP32) { + spriteAtlas = img_convert(spriteAtlas, NULL, IMAGE_BPP32, 0, 0); + if (!spriteAtlas) { + dbg_printf("spriteAtlas convert error\n"); + exit(-1); + } + } + dbg_printf("spriteAtlas->Type = %d\n", spriteAtlas->Type); + screenImage = img_create(DEFAULT_WIDTH, 200, IMAGE_BPP32); + // asm_inline("emms"); // doenst need bec. libimg functions used here does not use mmx (=> does not currept fpu state) +} + +// void save_fpu_state(void *fpustate) { +// __asm__("fsave %0" : "=m" (*fpustate) : : "memory"); +// } + +// void restore_fpu_state(const void *fpustate) { +// __asm__("fnsave %0" : : "m" (*fpustate)); +// } + + +void graphicsBlitAtlasImage(int atlasX, int atlasY, int destX, int destY, int w, int h, bool center) { + // dbg_printf("start graphicsBlitAtlasImage ax = %d ay = %d dx = %d dy = %d w = %d h = %d %x %x\n", atlasX, atlasY, destX, destY, w, h, screenImage, spriteAtlas); + + // asm_inline("int $3"); + + int screen_width = (int)screenImage->Width; + int screen_height = (int)screenImage->Height; + + if (destX >= screen_width) { + return; + } + if (destY >= screen_height) { + return; + } + + if (destX < 0) { + atlasX -= destX; + w = destX + w; + destX = 0; + } + if (destX + w > screen_width) { + w = screen_width - destX; + } + + if (destY < 0) { + atlasY -= destY; + h = destY + h; + destY = 0; + } + if (destY + h > screen_height) { + h = screen_height - destY; + } + + if (w <= 0 || h <= 0) { + return; + } + + //printf("start graphicsBlitAtlasImage ax = %d ay = %d dx = %d dy = %d w = %d h = %d %x %x\n\n", atlasX, atlasY, destX, destY, w, h, screenImage, spriteAtlas); + + // asm_inline("int $3"); + /*unsigned char buf[512]; + save_fpu_state(buf); + img_blend(screenImage, spriteAtlas, destX, destY, atlasX, atlasY, w, h); + restore_fpu_state(buf);*/ + + img_blend(screenImage, spriteAtlas, destX, destY, atlasX, atlasY, w, h); + asm_inline("emms"); + + // dbg_printf("end graphicsBlitAtlasImage\n\n"); +} + +void graphicsFillBackground(unsigned r, unsigned g, unsigned b) { + img_fill_color(screenImage, screenImage->Width, screenImage->Height, (0xFF << 24) | (r << 16) | (g << 8) | b); +} + +void graphicsRender() { + // don't redraw window on each frame. redraw window only when redraw event (called when widow moved e.g.) + //_ksys_start_draw(); + //_ksys_create_window(win_pos.x, win_pos.y, screenImage->Width + 10, screenImage->Height + 29, WINDOW_TITLE, sys_color_table.work_area, 0x54); // 0x54. note: C = 1 !! + img_draw(screenImage, 5, 24, screenImage->Width, screenImage->Height, 0, 0); + //ksys_draw_bitmap_palette(screenImage->Data, 5, 24, screenImage->Width, screenImage->Height, 32, 0, 0); + // ksys_blitter_params_t bp = {5, 24, screenImage->Width, screenImage->Height, 0, 0, screenImage->Width, screenImage->Height, screenImage->Data, screenImage->Width*4}; + // _ksys_blitter(0, &bp); + //_ksys_end_draw(); +} + +void graphicsDelay(int ms) { + // dbg_printf("ms = %d\n", ms); + _ksys_delay(ms/10 ? ms/10 : 2); +} + + +void graphicsDestroy() { + img_destroy(screenImage); + img_destroy(spriteAtlas); +} diff --git a/programs/games/dino/graphics.h b/programs/games/dino/graphics.h new file mode 100644 index 000000000..b17dfba9d --- /dev/null +++ b/programs/games/dino/graphics.h @@ -0,0 +1,41 @@ +#ifndef GRAPHICS_H +#define GRAPHICS_H + +#include +#include +#include + +#include +#include + +#include "config.h" + +#define ATLAS_CACTUS_LARGE_X 332 +#define ATLAS_CACTUS_LARGE_Y 2 +#define ATLAS_CACTUS_SMALL_X 228 +#define ATLAS_CACTUS_SMALL_Y 2 +#define ATLAS_CLOUD_X 86 +#define ATLAS_CLOUD_Y 2 +#define ATLAS_HORIZON_X 2 +#define ATLAS_HORIZON_Y 54 +#define ATLAS_MOON_X 484 +#define ATLAS_MOON_Y 2 +#define ATLAS_PTERODACTYL_X 134 +#define ATLAS_PTERODACTYL_Y 2 +#define ATLAS_RESTART_X 2 +#define ATLAS_RESTART_Y 2 +#define ATLAS_TEXT_SPRITE_X 655 +#define ATLAS_TEXT_SPRITE_Y 2 +#define ATLAS_TREX_X 848 +#define ATLAS_TREX_Y 2 +#define ATLAS_STAR_X 645 +#define ATLAS_STAR_Y 2 + +void graphicsInit(); +void graphicsBlitAtlasImage(int atlasX, int atlasY, int destX, int destY, int w, int h, bool center); +void graphicsFillBackground(unsigned r, unsigned g, unsigned b); +void graphicsRender(); +void graphicsDelay(int ms); +void graphicsDestroy(); + +#endif diff --git a/programs/games/dino/horizon.c b/programs/games/dino/horizon.c new file mode 100644 index 000000000..c3ab9469f --- /dev/null +++ b/programs/games/dino/horizon.c @@ -0,0 +1,147 @@ +#include "horizon.h" + +Horizon horizon; + +void horizonInit(int dim_width, double gapCoefficient) { + horizon.dim_width = dim_width; + horizon.gapCoefficient = gapCoefficient; + horizon.obstacles = ulist_create(); + horizon.obstacleHistory = ulist_create(); + horizon.clouds = ulist_create(); + + horizonAddCloud(); + + horizonLineInit(); +} + +void horizonUpdate(int deltaTime, double currentSpeed, bool updateObstacles, bool showNightMode) { + //horizon.runningTime += deltaTime; + horizonLineUpdate(deltaTime, currentSpeed); + // if (currentSpeed != currentSpeed) { + // currentSpeed = 6.0; + // } + // horizon.nightMode.update(showNightMode); + horizonUpdateClouds(deltaTime, currentSpeed); + if (updateObstacles) { + horizonUpdateObstacles(deltaTime, currentSpeed); + } +} + +void horizonUpdateClouds(int deltaTime, double speed) { + //printf("horizonUpdateClouds()\n"); + double cloudSpeed = HORIZON_BG_CLOUD_SPEED / 1000 * deltaTime * speed; + int numClouds = ulist_size(horizon.clouds); + //printf("horizonUpdateClouds() %d\n", numClouds); + + if (numClouds) { + Node *cloudNode = horizon.clouds->tail; + while (cloudNode != NULL) { + cloudUpdate(cloudNode->data, cloudSpeed); + cloudNode = cloudNode->prev; + } + Cloud *lastCloud = horizon.clouds->tail->data; + // Check for adding a new cloud + if (numClouds < HORIZON_MAX_CLOUDS && (horizon.dim_width - lastCloud->xPos) > lastCloud->cloudGap && HORIZON_CLOUD_FREQUENCY > (double)rand()/RAND_MAX) { + horizonAddCloud(); + } + // Remove expired clouds + cloudNode = horizon.clouds->head; + while (cloudNode != NULL) { + Node* cloudNodeNext = cloudNode->next; + Cloud* c = cloudNode->data; + if (c->remove) { + ulist_remove(horizon.clouds, cloudNode); + } + cloudNode = cloudNodeNext; + } + } + else { + horizonAddCloud(); + } +} + +void horizonUpdateObstacles(int deltaTime, double currentSpeed) { + //printf("horizonUpdateObstacles()\n"); + // Obstacles, move to Horizon layer + Node* obNode = horizon.obstacles->head; + while (obNode != NULL) { + Node* obNodeNext = obNode->next; + Obstacle* ob = obNode->data; + obstacleUpdate(ob, deltaTime, currentSpeed); + // Clean up existing obstacles + if (ob->remove) { + //ulist_remove(horizon.obstacles, obNode); + //ulist_print(horizon.obstacles); + ulist_remove_front(horizon.obstacles); + //ulist_print(horizon.obstacles); + //puts(""); + } + obNode = obNodeNext; + } + + if (ulist_size(horizon.obstacles) > 0) { + Obstacle *lastObstacle = horizon.obstacles->tail->data; + + if (lastObstacle && !lastObstacle->followingObstacleCreated && obstacleIsVisible(lastObstacle) && (lastObstacle->xPos + lastObstacle->width + lastObstacle->gap) < horizon.dim_width) { + horizonAddNewObstacle(currentSpeed); + lastObstacle->followingObstacleCreated = true; + } + } + else { + // Create new obstacles. + horizonAddNewObstacle(currentSpeed); + } +} + +void horizonAddNewObstacle(double currentSpeed) { + int obstacleTypeIndex = getRandomNumber(0, sizeof(obstacleTypeConfigs)/sizeof(ObstacleTypeConfig) - 1); + ObstacleTypeConfig *otc = &obstacleTypeConfigs[obstacleTypeIndex]; + + // Check for multiples of the same type of obstacle. + // Also check obstacle is available at current speed. + if (horizonDuplicateObstacleCheck(otc->type) || currentSpeed < otc->minSpeed) { + horizonAddNewObstacle(currentSpeed); + } + else { + Obstacle* ob = malloc(sizeof(Obstacle)); + obstacleInit(ob, otc, horizon.dim_width, horizon.gapCoefficient, currentSpeed, otc->width); + ulist_push_back(horizon.obstacles, ob); + ulist_push_front(horizon.obstacleHistory, &(otc->type)); + if (ulist_size(horizon.obstacleHistory) > 1) { + ulist_splice(horizon.obstacleHistory, RUNNER_MAX_OBSTACLE_DUPLICATION); + } + } +} + +bool horizonDuplicateObstacleCheck(ObstacleType nextObstacleType) { + //printf("horizonDuplicateObstacleCheck(%d)\n", nextObstacleType); + int duplicateCount = 0; + Node* ohNode = horizon.obstacleHistory->head; + while (ohNode != NULL) { + //printf("%d\n", *(int*)ohNode->data); + duplicateCount = *(int*)ohNode->data == nextObstacleType ? duplicateCount + 1 : 0; + ohNode = ohNode->next; + } + //printf("duplicateCount = %d\n\n", duplicateCount); + return duplicateCount >= RUNNER_MAX_OBSTACLE_DUPLICATION; +} + +void horizonReset() { + // printf("horizonReset() !!\n"); + ulist_destroy(horizon.obstacles); + horizon.obstacles = ulist_create(); + horizonLineReset(); +} + +//void horizonResize(int width, int height) { +// +//} + +void horizonAddCloud() { + Cloud* c = malloc(sizeof(Cloud)); + cloudInit(c, horizon.dim_width); + //printf("horizonAddCloud() %d -> ", ulist_size(horizon.obstacles)); + ulist_push_back(horizon.clouds, c); + //printf("%d\n", ulist_size(horizon.obstacles)); +} + diff --git a/programs/games/dino/horizon.h b/programs/games/dino/horizon.h new file mode 100644 index 000000000..7855ab87e --- /dev/null +++ b/programs/games/dino/horizon.h @@ -0,0 +1,41 @@ +#ifndef HORIZON_H +#define HORIZON_H + +#include +#include +#include "obstacle.h" +#include "cloud.h" +#include "horizon_line.h" +#include "runner.h" +#include "graphics.h" +#include "ulist.h" + +#define HORIZON_BG_CLOUD_SPEED 0.2 +#define HORIZON_BUMPY_THRESHOLD 0.3 +#define HORIZON_CLOUD_FREQUENCY 0.5 +#define HORIZON_HORIZON_HEIGHT 16 +#define HORIZON_MAX_CLOUDS 6 + +typedef struct { + int dim_width; + double gapCoefficient; + Ulist* obstacles; + Ulist* obstacleHistory; + // nightMode + Ulist* clouds; +} Horizon; + +extern Horizon horizon; + +void horizonInit(int dim_width, double gapCoefficient); +void horizonUpdate(int deltaTime, double currentSpeed, bool updateObstacles, bool showNightMode); +void horizonUpdateClouds(int deltaTime, double speed); +void horizonUpdateObstacles(int deltaTime, double currentSpeed); +//void horizonRemoveFirstObstacle(); +void horizonAddNewObstacle(double currentSpeed); +bool horizonDuplicateObstacleCheck(ObstacleType nextObstacleType); +void horizonReset(); +//void horizonResize(int width, int height); +void horizonAddCloud(); + +#endif diff --git a/programs/games/dino/horizon_line.c b/programs/games/dino/horizon_line.c new file mode 100644 index 000000000..76f69a6df --- /dev/null +++ b/programs/games/dino/horizon_line.c @@ -0,0 +1,56 @@ +#include "horizon_line.h" + +HorizonLine horizonLine; + +void horizonLineInit() { + horizonLine.width = HORIZON_LINE_WIDTH; + horizonLine.height = HORIZON_LINE_HEIGHT; + horizonLine.sourceXPos[0] = ATLAS_HORIZON_X; + horizonLine.sourceXPos[1] = ATLAS_HORIZON_X + horizonLine.width; + horizonLine.bumpThreshold = 0.5; + horizonLine.xPos[0] = 0; + horizonLine.xPos[1] = horizonLine.width; + horizonLine.yPos = HORIZON_LINE_YPOS; + horizonLineDraw(); +} + +void horizonLineDraw() { + //printf("horizonLineDraw(); xPos[0] = %d, xPos[1] = %d, yPos = %d, width = %d, height = %d\n", horizonLine.xPos[0], horizonLine.xPos[1], horizonLine.yPos, horizonLine.width, horizonLine.height); + graphicsBlitAtlasImage(horizonLine.sourceXPos[0], ATLAS_HORIZON_Y, horizonLine.xPos[0], horizonLine.yPos, horizonLine.width, horizonLine.height, false); + graphicsBlitAtlasImage(horizonLine.sourceXPos[1], ATLAS_HORIZON_Y, horizonLine.xPos[1], horizonLine.yPos, horizonLine.width, horizonLine.height, false); +} + +int horizonLineGetRandomType() { + return (double)rand() / RAND_MAX > horizonLine.bumpThreshold ? horizonLine.width : 0; +} + +void horizonLineUpdateXPos(int pos, int increment) { + int line1 = pos; + int line2 = pos == 0 ? 1 : 0; + + horizonLine.xPos[line1] -= increment; + horizonLine.xPos[line2] = horizonLine.xPos[line1] + horizonLine.width; + + if (horizonLine.xPos[line1] <= -horizonLine.width) { + horizonLine.xPos[line1] += horizonLine.width * 2; + horizonLine.xPos[line2] = horizonLine.xPos[line1] - horizonLine.width; + horizonLine.sourceXPos[line1] = horizonLineGetRandomType() + ATLAS_HORIZON_X; + } +} + +void horizonLineUpdate(int deltaTime, double speed) { + int increment = floor(speed * (FPS / 1000.0) * deltaTime); + if (horizonLine.xPos[0] <= 0) { + horizonLineUpdateXPos(0, increment); + } + else { + horizonLineUpdateXPos(1, increment); + } + // asm_inline("int $3"); + horizonLineDraw(); +} + +void horizonLineReset() { + horizonLine.xPos[0] = 0; + horizonLine.xPos[1] = horizonLine.width; +} diff --git a/programs/games/dino/horizon_line.h b/programs/games/dino/horizon_line.h new file mode 100644 index 000000000..d650ff80c --- /dev/null +++ b/programs/games/dino/horizon_line.h @@ -0,0 +1,33 @@ +#ifndef HORIZON_LINE_H +#define HORIZON_LINE_H + +#include +#include +#include +#include +#include "config.h" +#include "graphics.h" + +#define HORIZON_LINE_WIDTH 600 +#define HORIZON_LINE_HEIGHT 12 +#define HORIZON_LINE_YPOS 127 + +typedef struct { + int width; + int height; + int sourceXPos[2]; + int xPos[2]; + int yPos; + double bumpThreshold; +} HorizonLine; + +extern HorizonLine horizonLine; + +void horizonLineInit(); +void horizonLineDraw(); +int horizonLineGetRandomType(); +void horizonLineUpdateXPos(int pos, int increment); +void horizonLineUpdate(int deltaTime, double speed); +void horizonLineReset(); + +#endif diff --git a/programs/games/dino/main.c b/programs/games/dino/main.c new file mode 100644 index 000000000..6005bf2a2 --- /dev/null +++ b/programs/games/dino/main.c @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "misc.h" +#include "graphics.h" +#include "distance_meter.h" +#include "cloud.h" +#include "obstacle.h" +#include "horizon_line.h" +#include "trex.h" +#include "runner.h" + +uint8_t keyboard_layout[128]; + +extern ksys_colors_table_t sys_color_table; + extern ksys_pos_t win_pos; + extern Image* screenImage; + extern Image* spriteAtlas; + +int main(int argc, char* args[]) { + srand((unsigned int)time(NULL)); // Seed the random number generator + + graphicsInit(); + + runnerInit(); + + dbg_printf("dino started\n"); + + _ksys_set_event_mask(0xC0000027); // ! + _ksys_set_key_input_mode(KSYS_KEY_INPUT_MODE_SCANC); + _ksys_keyboard_layout(KSYS_KEYBOARD_LAYOUT_NORMAL, keyboard_layout); + + int ext_code = 0; + uint8_t old_mode = 0; + + bool quit = false; + while (quit == false) { + int frameStartTime = getTimeStamp(); + //printf("frameStartTime = %d\n", frameStartTime); + uint32_t kos_event = _ksys_check_event(); + switch (kos_event) { + case KSYS_EVENT_BUTTON: + switch (_ksys_get_button()){ + case 1: + quit = true; + break; + default: + break; + } + break; + case KSYS_EVENT_KEY: + { + ksys_oskey_t key = _ksys_get_key(); + uint8_t scancode = key.code; + if (scancode == 0xE0 || scancode == 0xE1) { + ext_code = scancode; + break; + } + if (ext_code == 0xE1 && (scancode & 0x7F) == 0x1D) { + break; + } + if (ext_code == 0xE1 && scancode == 0xC5) { + ext_code = 0; + break; + } + uint8_t code = keyboard_layout[scancode & 0x7F]; + + if (ext_code == 0xE0) { + code -= 96; + } + ext_code = 0; + + if (scancode < 128) { // KEYDOWN + //dbg_printf("Keydown: key = 0x%x, scancode = 0x%x, code = 0x%x (%u) state = 0x%x\n", key.val, scancode, code, code, key.state); + //dbg_printf("keydown c = %u\n", key.code); + runnerOnKeyDown(code); + } else { // KEYUP + //dbg_printf("Keyup: key = 0x%x, scancode = 0x%x, code = 0x%x (%u) state = 0x%x\n", key.val, scancode, code, code, key.state); + //dbg_printf("keyup c = %u\n", key.code); + runnerOnKeyUp(code); + } + } + break; + case KSYS_EVENT_REDRAW: + //dbg_printf("KSYS_EVENT_REDRAW\n"); + _ksys_start_draw(); + _ksys_create_window(win_pos.x, win_pos.y, screenImage->Width + 10, screenImage->Height + 29, WINDOW_TITLE, sys_color_table.work_area, 0x54); // 0x54. note: C = 1 !! + graphicsRender(); + _ksys_end_draw(); + break; + default: + break; + } + + if (runner.nextUpdateScheduled) { + //printf("runner update! %u\n", getTimeStamp()); + //dbg_printf("runnerUpdate\n"); + runnerUpdate(); + } + else { + if (runner.skipUpdateNow) { + //printf("Skipped one update\n"); + runner.nextUpdateScheduled = true; + runner.skipUpdateNow = false; + } + } + + int frameTime = getTimeStamp() - frameStartTime; + if (frameTime < 0) { + frameTime = DELTA_MS_DEFAULT; + } +#define FRAME_TIME 20 //16 + // dbg_printf("frameTime = %d\n", frameTime); + if (frameTime < FRAME_TIME) { // 1000ms/60frames = 16.(6) + // printf("frameTime = %d\n", frameTime); + if (runner.crashed) { + // dbg_printf("runner.timeAfterCrashedMs +=\n"); + runner.timeAfterCrashedMs += FRAME_TIME - frameTime; + } + graphicsDelay(FRAME_TIME - frameTime); + } + } + + graphicsDestroy(); + + return 0; +} diff --git a/programs/games/dino/misc.c b/programs/games/dino/misc.c new file mode 100644 index 000000000..a9157df53 --- /dev/null +++ b/programs/games/dino/misc.c @@ -0,0 +1,28 @@ +#include "misc.h" + +int getRandomNumber(int _min, int _max) { + return rand() % (_max - _min + 1) + _min; +} + +void intToStr(int num, int ndigits, char* result) { + char num_str[16]; // 16 more than enough for int + sprintf(num_str, "%d", num); // Convert num to a string + if (strlen(num_str) > ndigits) { + // Copy only the last ndigits to result + strcpy(result, num_str + strlen(num_str) - ndigits); + } + else { + // Pad the string with leading zeros until it reaches a length of ndigits + size_t z = ndigits - strlen(num_str); + for (size_t i = 0; i < z; i++) { + result[i] = '0'; + } + strcpy(result + z, num_str); + } +} + +int getTimeStamp() { // in ms + uint64_t x = 0; + x = _ksys_get_ns_count(); + return (x/1000000); +} diff --git a/programs/games/dino/misc.h b/programs/games/dino/misc.h new file mode 100644 index 000000000..4c8810e8e --- /dev/null +++ b/programs/games/dino/misc.h @@ -0,0 +1,13 @@ +#ifndef MISC_H +#define MISC_H + +#include +#include +#include +#include + +int getRandomNumber(int _min, int _max); +void intToStr(int num, int ndigits, char* result); +int getTimeStamp(); + +#endif diff --git a/programs/games/dino/obstacle.c b/programs/games/dino/obstacle.c new file mode 100644 index 000000000..d176310ae --- /dev/null +++ b/programs/games/dino/obstacle.c @@ -0,0 +1,156 @@ +#include "obstacle.h" + +ObstacleTypeConfig obstacleTypeConfigs[3] = { + { + .type = CACTUS_SMALL, + .width = 17, + .height = 35, + .yPos = 105, + .multipleSpeed = 4, + .minGap = 120, + .minSpeed = 0, + .collisionBoxesCount = 3, + .collisionBoxes = { + {.x = 0, .y = 7, .width = 5, .height = 27}, + {.x = 4, .y = 0, .width = 6, .height = 34}, + {.x = 10, .y = 4, .width = 7, .height = 14} + }, + .numFrames = 1, + .speedOffset = 0 + }, + { + .type = CACTUS_LARGE, + .width = 25, + .height = 50, + .yPos = 90, + .multipleSpeed = 7, + .minGap = 120, + .minSpeed = 0, + .collisionBoxesCount = 3, + .collisionBoxes = { + {.x = 0, .y = 12, .width = 7, .height = 38}, + {.x = 8, .y = 0, .width = 7, .height = 49}, + {.x = 13, .y = 10, .width = 10, .height = 38} + }, + .numFrames = 1, + .speedOffset = 0 + }, + { + .type = PTERODACTYL, + .width = 46, + .height = 40, + .yPos = -1, + .yPosArrSize = 3, + .yPosArr = {100, 75, 50}, + .multipleSpeed = 999, + .minGap = 150, + .minSpeed = 8.5, + .collisionBoxesCount = 5, + .collisionBoxes = { + {.x = 15, .y = 15, .width = 16, .height = 5}, + {.x = 18, .y = 21, .width = 24, .height = 6}, + {.x = 2, .y = 14, .width = 4, .height = 3}, + {.x = 6, .y = 10, .width = 4, .height = 7}, + {.x = 10, .y = 8, .width = 6, .height = 9} + }, + .numFrames = 2, + .frameRate = 1000 / 6, + .speedOffset = 0.8 + } +}; + +int obstacleSpritePosX[3] = { ATLAS_CACTUS_SMALL_X, ATLAS_CACTUS_LARGE_X, ATLAS_PTERODACTYL_X}; +int obstacleSpritePosY[3] = { ATLAS_CACTUS_SMALL_Y, ATLAS_CACTUS_LARGE_Y, ATLAS_PTERODACTYL_Y}; + + +void obstacleInit(Obstacle* ob, const ObstacleTypeConfig *otc, int dim_width, double gapCoefficient, double speed, int opt_xOffset) { + ob->typeConfig = *otc; + ob->gapCoefficient = gapCoefficient; + ob->size = getRandomNumber(1, OBSTACLE_MAX_OBSTACLE_LENGTH); + ob->remove = false; + ob->xPos = dim_width + opt_xOffset; + ob->yPos = 0; + + // For animated obstacles + ob->currentFrame = 0; + ob->timer = 0; + + ob->followingObstacleCreated = false; + + if (ob->size > 1 && ob->typeConfig.multipleSpeed > speed) { // NOTE what it this? + ob->size = 1; + } + ob->width = ob->typeConfig.width * ob->size; + + if (ob->typeConfig.yPos == -1) { + ob->yPos = ob->typeConfig.yPosArr[getRandomNumber(0, ob->typeConfig.yPosArrSize)]; + } + else { + ob->yPos = ob->typeConfig.yPos; + } + + obstacleDraw(ob); + + // Make collision box adjustments, + // Central box is adjusted to the size as one box. + // ____ ______ ________ + // _| |-| _| |-| _| |-| + // | |<->| | | |<--->| | | |<----->| | + // | | 1 | | | | 2 | | | | 3 | | + // |_|___|_| |_|_____|_| |_|_______|_| + // + + if (ob->size > 1) { + ob->typeConfig.collisionBoxes[1].width = ob->width - ob->typeConfig.collisionBoxes[0].width - ob->typeConfig.collisionBoxes[2].width; + ob->typeConfig.collisionBoxes[2].x = ob->width - ob->typeConfig.collisionBoxes[2].width; + } + + // For obstacles that go at a different speed from the horizon + if (ob->typeConfig.speedOffset) { + ob->typeConfig.speedOffset = (double)rand() / RAND_MAX > 0.5 ? ob->typeConfig.speedOffset : -ob->typeConfig.speedOffset; + } + ob->gap = obstacleGetGap(ob, ob->gapCoefficient, speed); +} + +void obstacleDraw(const Obstacle *ob) { + int sourceWidth = ob->typeConfig.width; + int sourceHeight = ob->typeConfig.height; + int sourceX = (sourceWidth * ob->size) * (0.5 * ((double)ob->size - 1)) + obstacleSpritePosX[ob->typeConfig.type]; + if (ob->currentFrame > 0) { + sourceX += sourceWidth*ob->currentFrame; + } + //dbg_printf("od ax=%u, ay=%u, dx=%u, dy=%u, w=%u, h=%u\n", sourceX, obstacleSpritePosY[ob->typeConfig.type], ob->xPos, ob->yPos, sourceWidth*ob->size, sourceHeight); + graphicsBlitAtlasImage(sourceX, obstacleSpritePosY[ob->typeConfig.type], ob->xPos, ob->yPos, sourceWidth*ob->size, sourceHeight, false); +} + +void obstacleUpdate(Obstacle *ob, int deltaTime, double speed) { + if (!ob->remove) { + double dx = floor(((speed + ob->typeConfig.speedOffset)*FPS/1000.)*deltaTime); + //dbg_printf("sp = %lf, ots = %lf, dx = %d, xpos = %d\n", speed, ob->typeConfig.speedOffset, (int)dx, ob->xPos - dx); + ob->xPos -= dx;//floor(((speed + ob->typeConfig.speedOffset)*FPS/1000.)*deltaTime); + } + // Update frames + if (ob->typeConfig.numFrames > 1) { + ob->timer += deltaTime; + if (ob->timer >= ob->typeConfig.frameRate) { + ob->currentFrame = ob->currentFrame == ob->typeConfig.numFrames - 1 ? 0 : ob->currentFrame + 1; + ob->timer = 0; + } + } + obstacleDraw(ob); + if (!obstacleIsVisible(ob)) { + ob->remove = true; + } +} + +int obstacleGetGap(const Obstacle *ob, double gapCoefficient, double speed) { + int minGap = round(ob->width * speed + ob->typeConfig.minGap * gapCoefficient); + int maxGap = round(minGap * OBSTACLE_MAX_GAP_COEFFICIENT); + return getRandomNumber(minGap, maxGap); +} + +bool obstacleIsVisible(const Obstacle* ob) { + return ob->xPos + ob->width > 0; +} + + diff --git a/programs/games/dino/obstacle.h b/programs/games/dino/obstacle.h new file mode 100644 index 000000000..112ba4a09 --- /dev/null +++ b/programs/games/dino/obstacle.h @@ -0,0 +1,67 @@ +#ifndef OBSTACLE_H +#define OBSTACLE_H + +#include +#include +#include +#include "graphics.h" +#include "misc.h" +#include "config.h" +#include "collisionbox.h" + +// Coefficient for calculating the maximum gap +#define OBSTACLE_MAX_GAP_COEFFICIENT 1.5 + +// Maximum obstacle grouping count +#define OBSTACLE_MAX_OBSTACLE_LENGTH 3 + +typedef enum { + CACTUS_SMALL = 0, + CACTUS_LARGE = 1, + PTERODACTYL = 2 +} ObstacleType; + +extern int obstacleSpritePosX[3]; +extern int obstacleSpritePosY[3]; + +typedef struct { + ObstacleType type; + int width; + int height; + int yPos; + int yPosArrSize; + int yPosArr[3]; // used if yPos is -1 + int multipleSpeed; + int minGap; + int minSpeed; + int collisionBoxesCount; + CollisionBox collisionBoxes[5]; + int numFrames; + double frameRate; + double speedOffset; +} ObstacleTypeConfig; + +typedef struct { + ObstacleTypeConfig typeConfig; + double gapCoefficient; + int size; + bool remove; + int xPos; + int yPos; + int width; + int gap; + // double speedOffset; + int currentFrame; + int timer; + bool followingObstacleCreated; +} Obstacle; + +extern ObstacleTypeConfig obstacleTypeConfigs[3]; + +void obstacleInit(Obstacle *ob, const ObstacleTypeConfig *otc, int dim_width, double gapCoefficient, double speed, int opt_xOffset); +void obstacleDraw(const Obstacle* ob); +void obstacleUpdate(Obstacle* ob, int deltaTime, double speed); +int obstacleGetGap(const Obstacle* ob, double gapCoefficient, double speed); +bool obstacleIsVisible(const Obstacle* ob); + +#endif diff --git a/programs/games/dino/runner.c b/programs/games/dino/runner.c new file mode 100644 index 000000000..52ca27d15 --- /dev/null +++ b/programs/games/dino/runner.c @@ -0,0 +1,333 @@ +#include "runner.h" + +int aaaaaaa[10000]; +Runner runner; +int bbbbbb[10000]; + +void runnerInit() { + runner.distanceRan = 0; + runner.highestScore = 0; + runner.time = 0; + runner.msPerFrame = 1000.0 / FPS; + runner.currentSpeed = RUNNER_SPEED; + runner.activated = false; + runner.playing = false; + runner.crashed = false; + runner.timeAfterCrashedMs = 0; + runner.paused = false; + runner.inverted = false; + runner.playingIntro = false; + runner.isRunning = false; // is running or game stopped + runner.invertTimer = 0; + runner.playCount = 0; + runner.nextUpdateScheduled = false; + runner.skipUpdateNow = false; + // TODO sound + // runnerLoadImages(); + runnerAdjustDimensions(); + // setSpeed + graphicsFillBackground(0xF7, 0xF7, 0xF7); + + gameOverPanelInit(runner.width, runner.height); + + horizonInit(runner.width, RUNNER_GAP_COEFFICIENT); + distanceMeterInit(runner.width); + trexInit(); + + // this.startListening(); + runnerUpdate(); + //window.addEventListener(Runner.events.RESIZE, this.debounceResize.bind(this)); +} + +void runnerAdjustDimensions() { + runner.width = DEFAULT_WIDTH; + runner.height = RUNNER_DEFAULT_HEIGHT; + // distance meter ... +} + +void runnerOnKeyDown(int key) { + if (!runner.crashed && (key == RUNNER_KEYCODE_JUMP_1 || key == RUNNER_KEYCODE_JUMP_2)) { + if (!runner.playing) { + // this.loadSounds(); // TODO + runner.playing = true; + //printf("first jump! %u\n", getTimeStamp()); + runnerUpdate(); + runner.nextUpdateScheduled = false; + runner.skipUpdateNow = true; + } + // Play sound effect and jump on starting the game for the first time. + if (!trex.jumping && !trex.ducking) { + // this.playSound(this.soundFx.BUTTON_PRESS); // TODO + trexStartJump(runner.currentSpeed); + } + } + if (runner.playing && !runner.crashed && key == RUNNER_KEYCODE_DUCK) { + if (trex.jumping) { + // Speed drop, activated only when jump key is not pressed. + trexSetSpeedDrop(); + } + //else if (!trex.jumping &&!trex.ducking) { + else if (!trex.ducking) { + // Duck + trexSetDuck(true); + } + } +} + +void runnerOnKeyUp(int key) { + if (runner.isRunning && (key == RUNNER_KEYCODE_JUMP_1 || key == RUNNER_KEYCODE_JUMP_2)) { + trexEndJump(); + } + else if (key == RUNNER_KEYCODE_DUCK) { + trex.speedDrop = false; + trexSetDuck(false); + } + else if (runner.crashed) { + // Check that enough time has elapsed before allowing jump key to restart. + + int now = getTimeStamp(); + int deltaTime = now - runner.time; + // if (deltaTime < 0) { + // deltaTime = DELTA_MS_DEFAULT; + // runner.time = 0; + // } + + // dbg_printf(".now = %d .deltaTime = %d runner.time = %d\n", now, deltaTime, runner.time); + if (key == RUNNER_KEYCODE_RESTART || (runner.timeAfterCrashedMs >= RUNNER_GAMEOVER_CLEAR_TIME && (key == RUNNER_KEYCODE_JUMP_1 || key == RUNNER_KEYCODE_JUMP_2))) { + //dbg_printf("timeAfterCrashedMs = %d\n", runner.timeAfterCrashedMs); + runnerRestart(); + } + } + else if (runner.paused && (key == RUNNER_KEYCODE_JUMP_1 || key == RUNNER_KEYCODE_JUMP_2)) { + trexReset(); + runnerPlay(); + } +} + +void runnerClearCanvas() { + graphicsFillBackground(0xF7, 0xF7, 0xF7); + //graphicsRender(); +} + +void runnerUpdate() { + //dbg_printf("runnerUpdate() runner.playing = %d\n", runner.playing); + //runner.updatePending = false; + int now = getTimeStamp(); + //printf("now = %d\n", now); + int deltaTime = now - (runner.time ? runner.time : 0); + if (deltaTime < 0) { + deltaTime = DELTA_MS_DEFAULT; + } + // dbg_printf("runnerUpdate() deltaTime = %d\n", deltaTime); + runner.time = now; + if (runner.playing) { + //printf("runnerUpdate() %d\n", getTimeStamp()); + runnerClearCanvas(); + + if (trex.jumping) { + trexUpdateJump(deltaTime); + } + + runner.runningTime += deltaTime; + bool hasObstacles = runner.runningTime > RUNNER_CLEAR_TIME; + + // First jump triggers the intro. + if (trex.jumpCount == 1 && !runner.playingIntro) { + //printf("trex.jumpCount = %d\n", trex.jumpCount); + runnerPlayIntro(); + } + + // The horizon doesn't move until the intro is over. + if (runner.playingIntro) { + horizonUpdate(0, runner.currentSpeed, hasObstacles, false); + } + else { + deltaTime = !runner.activated ? 0 : deltaTime; + horizonUpdate(deltaTime, runner.currentSpeed, hasObstacles, runner.inverted); + } + + // Check for collisions. + bool collision = hasObstacles && runnerCheckForCollision(horizon.obstacles->head->data); + + if (!collision) { + runner.distanceRan += runner.currentSpeed * deltaTime / runner.msPerFrame; + + if (runner.currentSpeed < RUNNER_MAX_SPEED) { + runner.currentSpeed += RUNNER_ACCELERATION; + } + } + else { + runnerGameOver(); + } + + bool playAchievementSound = distanceMeterUpdate(deltaTime, (int)ceil(runner.distanceRan)); + + if (playAchievementSound) { + //this.playSound(this.soundFx.SCORE); // TODO + } + + /*// Night mode. + if (this.invertTimer > this.config.INVERT_FADE_DURATION) { + this.invertTimer = 0; + this.invertTrigger = false; + this.invert(); + } + else if (this.invertTimer) { + this.invertTimer += deltaTime; + } + else { + var actualDistance = + this.distanceMeter.getActualDistance(Math.ceil(this.distanceRan)); + + if (actualDistance > 0) { + this.invertTrigger = !(actualDistance % + this.config.INVERT_DISTANCE); + + if (this.invertTrigger&& this.invertTimer == = 0) { + this.invertTimer += deltaTime; + this.invert(); + } + } + }*/ + } + + runner.nextUpdateScheduled = false;// + if (runner.playing || (!runner.activated && trex.blinkCount < RUNNER_MAX_BLINK_COUNT)) { + trexUpdate(deltaTime, -1); + runner.nextUpdateScheduled = true; + } + + graphicsRender(); // blit all drawn to the screen + //printf("runner update end\n\n"); +} + +void runnerGameOver() { + // this.playSound(this.soundFx.HIT); // TODO + runnerStop(); + runner.crashed = true; + distanceMeter.achievement = false; + trexUpdate(100, TREX_STATUS_CRASHED); + + // Game over panel + gameOverPanelDraw(); + // Update the high score + if (runner.distanceRan > runner.highestScore) { + runner.highestScore = (int)ceil(runner.distanceRan); + distanceMeterSetHighScore(runner.highestScore); + } + // Reset the time clock + runner.time = getTimeStamp(); +} + +void runnerStop() { + runner.playing = false; + runner.paused = true; + runner.isRunning = false; +} + +void runnerPlay() { + if (!runner.crashed) { + runner.playing = true; + runner.paused = false; + trexUpdate(0, TREX_STATUS_RUNNING); + runner.time = getTimeStamp(); + runnerUpdate(); + } +} + +void runnerRestart() { + if (!runner.isRunning) { + runner.playCount++; + runner.runningTime = 0; + runner.playing = true; + runner.crashed = false; + runner.timeAfterCrashedMs = 0; + runner.distanceRan = 0; + runner.currentSpeed = RUNNER_SPEED; + runner.time = getTimeStamp(); + runnerClearCanvas(); + distanceMeterReset(runner.highestScore); + horizonReset(); + trexReset(); + //this.playSound(this.soundFx.BUTTON_PRESS); + //this.invert(true); + runner.isRunning = true; + runnerUpdate(); + } +} + +void runnerPlayIntro() { + //printf("runnerPlayIntro()\n"); + if (!runner.activated && !runner.crashed) { + runner.playingIntro = true; + trex.playingIntro = true; + runner.playing = true; + runner.activated = true; + } + else if (runner.crashed) { + runnerRestart(); + } +} + +void runnerStartGame() { + runner.runningTime = 0; + runner.playingIntro = false; + trex.playingIntro = false; + runner.playCount++; + runner.isRunning = true; +} + +CollisionBox createAdjustedCollisionBox(CollisionBox box, CollisionBox adjustment) { + return (CollisionBox){ .x = box.x + adjustment.x, .y = box.y + adjustment.y, .width = box.width, .height = box.height }; +} + +// Returns whether boxes intersected +bool boxCompare(CollisionBox tRexBox, CollisionBox obstacleBox) { + // Axis-Aligned Bounding Box method. + return (tRexBox.x < obstacleBox.x + obstacleBox.width && + tRexBox.x + tRexBox.width > obstacleBox.x && + tRexBox.y < obstacleBox.y + obstacleBox.height && + tRexBox.height + tRexBox.y > obstacleBox.y); +} + +bool runnerCheckForCollision(const Obstacle* obstacle) { + // Adjustments are made to the bounding box as there is a 1 pixel white + // border around the t-rex and obstacles. + CollisionBox tRexBox = { + .x = trex.xPos + 1, + .y = trex.yPos + 1, + .width = TREX_WIDTH - 2, + .height = TREX_HEIGHT - 2 }; + + CollisionBox obstacleBox = { + .x = obstacle->xPos + 1, + .y = obstacle->yPos + 1, + .width = obstacle->typeConfig.width * obstacle->size - 2, + .height = obstacle->typeConfig.height - 2 }; + + // Simple outer bounds check. + if (boxCompare(tRexBox, obstacleBox)) { + CollisionBox* tRexCollisionBoxes = &trexDuckingCollisionBox; + int tRexCollisionBoxesCount = 1; + if (!trex.ducking) { + tRexCollisionBoxes = trexRunningCollisionBox; + tRexCollisionBoxesCount = 6; + } + + // Detailed axis aligned box check. + for (int t = 0; t < tRexCollisionBoxesCount; t++) { + for (int i = 0; i < obstacle->typeConfig.collisionBoxesCount; i++) { + // Adjust the box to actual positions. + CollisionBox adjTrexBox = createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox); + CollisionBox adjObstacleBox = createAdjustedCollisionBox(obstacle->typeConfig.collisionBoxes[i], obstacleBox); + + if (boxCompare(adjTrexBox, adjObstacleBox)) { + return true;// [adjTrexBox, adjObstacleBox] ; + } + } + } + } + return false; +} + + diff --git a/programs/games/dino/runner.h b/programs/games/dino/runner.h new file mode 100644 index 000000000..2862c91d5 --- /dev/null +++ b/programs/games/dino/runner.h @@ -0,0 +1,91 @@ +#ifndef RUNNER_H +#define RUNNER_H + +#include +#include "config.h" +#include "ulist.h" +#include "graphics.h" +#include "horizon.h" +#include "distance_meter.h" +#include "game_over_panel.h" +#include "trex.h" + +#define RUNNER_DEFAULT_HEIGHT 150 + +#define RUNNER_ACCELERATION 0.001 +#define RUNNER_BG_CLOUD_SPEED 0.2 +#define RUNNER_BOTTOM_PAD 10 +#define RUNNER_CLEAR_TIME 3000 +#define RUNNER_CLOUD_FREQUENCY 0.5 +#define RUNNER_GAMEOVER_CLEAR_TIME 750 +#define RUNNER_GAP_COEFFICIENT 0.6 +#define RUNNER_GRAVITY 0.6 +#define RUNNER_INITIAL_JUMP_VELOCITY 12 +#define RUNNER_INVERT_FADE_DURATION 12000 +#define RUNNER_INVERT_DISTANCE 700 +#define RUNNER_MAX_BLINK_COUNT 3 +#define RUNNER_MAX_CLOUDS 6 +#define RUNNER_MAX_OBSTACLE_LENGTH 3 +#define RUNNER_MAX_OBSTACLE_DUPLICATION 2 +#define RUNNER_MAX_SPEED 13.0 +#define RUNNER_MIN_JUMP_HEIGHT 35 +#define RUNNER_MOBILE_SPEED_COEFFICIENT 1.2 +#define RUNNER_SPEED 6.0 +#define RUNNER_SPEED_DROP_COEFFICIENT 3 + +#define RUNNER_KEYCODE_JUMP_1 82 +#define RUNNER_KEYCODE_JUMP_2 32 +#define RUNNER_KEYCODE_DUCK 81 +#define RUNNER_KEYCODE_RESTART 13 + +typedef struct { + int width; + int height; + double distanceRan; + int highestScore; + int time; + int runningTime; + double msPerFrame; + double azazzaza; // + double currentSpeed; + // Ulist* obstacles; + bool activated; + bool playing; + bool crashed; + int timeAfterCrashedMs; + bool paused; + bool inverted; + bool invertTimer; + bool playingIntro; + bool isRunning; + // resizeTimerId_ + int playCount; + // soundFx + // audioContext + // images + // imagesLoaded + bool nextUpdateScheduled; + bool skipUpdateNow; +} Runner; + +extern Runner runner; + +void runnerInit(); +void runnerAdjustDimensions(); +//void runnerLoadImages(); + +void runnerClearCanvas(); + +void runnerPlayIntro(); +void runnerStartGame(); +void runnerUpdate(); +void runnerOnKeyDown(int key); +void runnerOnKeyUp(int key); +void runnerGameOver(); +void runnerStop(); +void runnerPlay(); +void runnerRestart(); + +bool runnerCheckForCollision(const Obstacle *obstacle); + +#endif diff --git a/programs/games/dino/sprites.h b/programs/games/dino/sprites.h new file mode 100644 index 000000000..dc3fe332f --- /dev/null +++ b/programs/games/dino/sprites.h @@ -0,0 +1,169 @@ +// Original chrome dino sprite pack +static const char sprites100[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x04, 0xd1, 0x00, 0x00, 0x00, 0x44, 0x08, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x23, 0xfc, + 0x41, 0x00, 0x00, 0x00, 0x02, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x00, 0x76, 0x93, 0xcd, 0x38, 0x00, + 0x00, 0x0a, 0x0e, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0xec, 0x9d, 0x5b, 0x76, 0xa4, 0xb8, 0x16, + 0x44, 0x83, 0xbb, 0xb8, 0x0f, 0x4d, 0x84, 0xa1, 0xd8, 0x03, 0x80, 0x4f, 0x0d, 0x4f, 0x9f, 0xa9, + 0x01, 0x54, 0x0d, 0x85, 0x89, 0xe8, 0x93, 0xae, 0x57, 0xac, 0xab, 0xaa, 0x68, 0xf2, 0x58, 0x2d, + 0xd2, 0x98, 0xf4, 0xd9, 0xbd, 0x3a, 0x13, 0x9f, 0x07, 0x95, 0x18, 0x57, 0x38, 0x24, 0x01, 0x05, + 0xc7, 0x71, 0x1c, 0xc7, 0x71, 0x1c, 0xe7, 0xc3, 0x31, 0xe0, 0x3b, 0x05, 0xf7, 0x08, 0x38, 0x8e, + 0x15, 0xc0, 0x84, 0x83, 0x29, 0x08, 0x6f, 0x2b, 0x43, 0xb8, 0x1f, 0xd8, 0x80, 0xa1, 0xb7, 0x85, + 0x65, 0x2c, 0x7c, 0x58, 0x82, 0xb4, 0x76, 0xac, 0xf8, 0xce, 0xd4, 0x11, 0x83, 0x55, 0xdd, 0x10, + 0x23, 0x5f, 0x5f, 0xf0, 0x93, 0x19, 0x40, 0xe6, 0x36, 0x32, 0x43, 0x3f, 0xa3, 0x33, 0xb8, 0xc9, + 0xca, 0x2a, 0xc4, 0x34, 0x63, 0xe0, 0x26, 0xcb, 0x19, 0xe2, 0xae, 0xea, 0xe6, 0xc7, 0x52, 0xa0, + 0x84, 0x0b, 0xd6, 0x5e, 0x80, 0x11, 0x00, 0x4a, 0xc4, 0x3d, 0x4a, 0x40, 0x07, 0xf2, 0xb3, 0xbc, + 0xae, 0x98, 0x70, 0x02, 0x25, 0xf2, 0x50, 0xf6, 0x02, 0xdb, 0x02, 0x6c, 0x43, 0x4f, 0x4b, 0x55, + 0xc6, 0xc2, 0xa3, 0x13, 0x4a, 0x7b, 0xc7, 0x84, 0xd3, 0x98, 0x80, 0x15, 0x77, 0x98, 0xa9, 0x64, + 0x94, 0xa0, 0xea, 0x3d, 0x4b, 0x21, 0x31, 0x42, 0x33, 0xb2, 0xec, 0xe5, 0x39, 0x71, 0x46, 0x5b, + 0xd0, 0x10, 0x4b, 0x40, 0xbf, 0x96, 0xff, 0xd0, 0x31, 0x6a, 0xda, 0x84, 0xe3, 0x28, 0x88, 0xa6, + 0x4b, 0xe3, 0x51, 0xf2, 0x50, 0x24, 0x40, 0x75, 0xc2, 0xb2, 0x0d, 0x1d, 0x2d, 0x55, 0x19, 0x0b, + 0x0f, 0x4f, 0xa8, 0x57, 0xb4, 0x3b, 0xc8, 0xba, 0x1f, 0x9b, 0xac, 0x3a, 0xac, 0xa6, 0xdd, 0xae, + 0x63, 0x54, 0x2f, 0xa3, 0x4e, 0xc9, 0xaa, 0x4c, 0xb4, 0x67, 0x14, 0xba, 0x2c, 0x59, 0xd9, 0x64, + 0x87, 0x6e, 0xc9, 0x7e, 0x33, 0xde, 0xc1, 0xa1, 0x45, 0xfc, 0x4e, 0xba, 0x72, 0xad, 0xf1, 0x9b, + 0x24, 0x9f, 0xac, 0x68, 0x14, 0x34, 0x5b, 0xd2, 0x3a, 0x8e, 0x7c, 0x45, 0xfd, 0x93, 0xbe, 0xe2, + 0x7a, 0xa8, 0xeb, 0xa1, 0x90, 0x9d, 0xea, 0x37, 0xdb, 0x99, 0x1e, 0x52, 0x3b, 0x61, 0xed, 0xb1, + 0x69, 0x5f, 0x7f, 0x0c, 0x3b, 0xa9, 0x5b, 0x33, 0xe6, 0x4c, 0x69, 0xe3, 0x98, 0x11, 0x32, 0x12, + 0xcd, 0x8c, 0xcd, 0xf5, 0x68, 0xb2, 0xee, 0xc8, 0x6e, 0xca, 0x3e, 0x1d, 0x23, 0xde, 0x89, 0x49, + 0x05, 0xee, 0x38, 0xc4, 0xa5, 0x59, 0xca, 0xa4, 0x81, 0x0d, 0x83, 0x4a, 0x55, 0x5d, 0x51, 0x22, + 0x1b, 0x59, 0xb5, 0x6c, 0xda, 0xf2, 0x78, 0xd4, 0x2b, 0xda, 0x74, 0x3b, 0x34, 0x8d, 0x31, 0x3a, + 0xdd, 0x8f, 0x4d, 0x58, 0xed, 0xba, 0xaf, 0x7c, 0x7d, 0x31, 0x4d, 0xc0, 0xcc, 0x31, 0xa4, 0xd6, + 0x64, 0x16, 0x66, 0xed, 0x81, 0x86, 0xde, 0x95, 0x68, 0xfc, 0xc4, 0x5e, 0xb9, 0x76, 0xd6, 0x08, + 0xf2, 0x65, 0x15, 0xcd, 0x3e, 0x72, 0x85, 0x23, 0xcf, 0x93, 0x26, 0x74, 0x4a, 0x2c, 0x41, 0x03, + 0xdb, 0x82, 0x4d, 0xa5, 0xaa, 0xaa, 0x28, 0x11, 0xb1, 0xfc, 0x2e, 0x68, 0x58, 0xb4, 0xa5, 0x05, + 0x1d, 0x3e, 0x3e, 0x9e, 0x09, 0xe7, 0x23, 0x36, 0xed, 0x85, 0x72, 0x76, 0x9f, 0x2c, 0x5f, 0x8a, + 0x7e, 0x49, 0x41, 0x9e, 0xeb, 0xad, 0x53, 0xb9, 0x61, 0x39, 0x63, 0xbf, 0x1d, 0xb5, 0xc0, 0xd2, + 0x5f, 0x7b, 0xbe, 0xa2, 0xa5, 0x88, 0x36, 0x32, 0xe6, 0x46, 0x6f, 0xb0, 0x4e, 0xc0, 0x54, 0x25, + 0x6a, 0x4a, 0xd3, 0xaa, 0x4a, 0x69, 0x76, 0x69, 0x25, 0xd6, 0xe6, 0x86, 0x81, 0xff, 0x2d, 0xc0, + 0x72, 0xab, 0xa4, 0x6a, 0xf8, 0xb3, 0x02, 0xfb, 0xb0, 0xe5, 0xf8, 0xe1, 0x63, 0xbf, 0x38, 0xf6, + 0x39, 0xb4, 0x49, 0x63, 0xfb, 0x4e, 0x6b, 0x5a, 0xff, 0xb9, 0x4b, 0xc3, 0xcb, 0xd7, 0x7d, 0x41, + 0xa3, 0xf3, 0xe2, 0xeb, 0x4c, 0x3f, 0xa6, 0x43, 0xca, 0xcc, 0x6c, 0x3d, 0xe4, 0x9c, 0x7f, 0x7c, + 0xa5, 0xcb, 0xa6, 0xce, 0x7d, 0x16, 0xdc, 0x7a, 0x6b, 0x4f, 0x60, 0x44, 0xaf, 0xa4, 0x09, 0xf6, + 0xd0, 0x64, 0x92, 0xd1, 0xe7, 0xd5, 0x28, 0xe1, 0x9c, 0xe1, 0xa3, 0x50, 0x82, 0x88, 0xe3, 0xd5, + 0x99, 0xf3, 0xbd, 0x31, 0x27, 0x93, 0x1a, 0xb3, 0x39, 0x57, 0xc6, 0x96, 0x53, 0xf7, 0xcb, 0xda, + 0xa7, 0x67, 0x44, 0xb7, 0xa4, 0xcd, 0xa6, 0x2b, 0x98, 0xcc, 0xc1, 0x07, 0x29, 0x88, 0xc6, 0xf5, + 0x65, 0x5a, 0x9d, 0x90, 0x10, 0x0d, 0x97, 0x56, 0x82, 0x15, 0xb0, 0x5b, 0x74, 0xe4, 0x7a, 0x3e, + 0xfc, 0x20, 0xb6, 0x38, 0xae, 0xf6, 0xf9, 0x31, 0xeb, 0x48, 0xeb, 0x5c, 0xda, 0x6a, 0xd7, 0x91, + 0x3c, 0xcb, 0x08, 0x73, 0xff, 0x6b, 0x5b, 0xcc, 0x64, 0x10, 0xc1, 0x0e, 0x1b, 0xe7, 0xd6, 0x5f, + 0x7b, 0xb6, 0xa2, 0xa5, 0x5a, 0xd2, 0x12, 0x10, 0x8f, 0xf2, 0x66, 0x46, 0xd5, 0x49, 0x73, 0x67, + 0x6d, 0x2d, 0x3a, 0x0c, 0x3d, 0x1f, 0x0e, 0xa1, 0xaf, 0x30, 0x8b, 0x66, 0x2c, 0x8e, 0x6e, 0xa0, + 0xa4, 0x55, 0xab, 0x99, 0x14, 0x2d, 0x0e, 0x3c, 0xbf, 0x27, 0xa9, 0x4a, 0xe4, 0xff, 0x31, 0xcd, + 0xd6, 0x0a, 0x29, 0x39, 0xf9, 0x33, 0x9e, 0x01, 0xf7, 0x7e, 0xff, 0x12, 0x41, 0xab, 0xb6, 0xd2, + 0x3b, 0x4c, 0x43, 0x4f, 0x55, 0x51, 0x29, 0xb1, 0x41, 0x44, 0xb5, 0x3a, 0xc6, 0x52, 0x0c, 0x05, + 0x92, 0xc0, 0xa3, 0x45, 0xab, 0x94, 0xb2, 0x97, 0x30, 0x3a, 0x3e, 0x11, 0x5f, 0xf8, 0xea, 0x38, + 0xc7, 0x79, 0xb4, 0xa4, 0x4b, 0x98, 0x29, 0x76, 0xf8, 0xb3, 0x95, 0x8b, 0x00, 0x06, 0x93, 0xd6, + 0x5c, 0x1f, 0x73, 0x72, 0xab, 0xe8, 0x20, 0xd9, 0xe8, 0x90, 0xfa, 0x86, 0xb5, 0x02, 0x59, 0x58, + 0x66, 0x8c, 0x71, 0x6b, 0xcc, 0xd9, 0x35, 0xee, 0x9c, 0x56, 0x8d, 0xd1, 0xa5, 0xfd, 0x88, 0xbd, + 0x02, 0xe0, 0x6b, 0x06, 0xa0, 0x1b, 0xba, 0xc9, 0xaf, 0x18, 0xd3, 0x42, 0xc9, 0x6a, 0x5b, 0x86, + 0xf3, 0xd4, 0xf3, 0x68, 0xf5, 0x68, 0x33, 0x22, 0xf5, 0x0d, 0x38, 0xa7, 0xc6, 0x8b, 0x69, 0x0b, + 0x62, 0xc3, 0xba, 0x65, 0x5d, 0x9d, 0x00, 0xed, 0x3c, 0x03, 0x4a, 0x88, 0xce, 0xfc, 0x6b, 0x02, + 0x88, 0x09, 0xc4, 0xea, 0xa0, 0x06, 0x86, 0x0e, 0x39, 0xfd, 0xe0, 0x2e, 0xed, 0xcb, 0x2b, 0xba, + 0x71, 0x9c, 0x11, 0x4a, 0x44, 0x37, 0x93, 0x6c, 0x5d, 0x80, 0xe5, 0x51, 0x8e, 0x8c, 0x89, 0xee, + 0xbb, 0x50, 0x63, 0xd3, 0x3e, 0x28, 0x8e, 0xd7, 0x19, 0x74, 0xbe, 0x02, 0xd8, 0xbd, 0x7b, 0x7c, + 0xde, 0x09, 0x66, 0xbe, 0x4b, 0xb1, 0xee, 0x47, 0xba, 0x59, 0xd1, 0xb3, 0xcf, 0xb9, 0x8a, 0x9a, + 0x24, 0x90, 0x33, 0x6b, 0x9f, 0x98, 0xf1, 0x28, 0x41, 0x4b, 0xfd, 0x77, 0x67, 0xee, 0xde, 0x31, + 0x1a, 0xcc, 0x6a, 0x9c, 0xef, 0xd2, 0x54, 0x42, 0x34, 0x71, 0x82, 0x1a, 0x19, 0x4f, 0xd1, 0x68, + 0x88, 0x99, 0xe3, 0xce, 0xf6, 0xd8, 0xb4, 0x32, 0x46, 0x39, 0x73, 0x9c, 0xa3, 0x15, 0x2d, 0xe2, + 0x13, 0x32, 0x6c, 0x0b, 0x9c, 0x13, 0x79, 0xe5, 0xa0, 0x93, 0xce, 0x48, 0x98, 0x79, 0xbf, 0xa7, + 0x26, 0xf2, 0x0c, 0x05, 0x5a, 0xb9, 0xe7, 0xef, 0x66, 0xa3, 0xd6, 0xdc, 0x67, 0xbd, 0x1f, 0xb9, + 0x46, 0xbc, 0xe8, 0x5f, 0x2f, 0x24, 0x04, 0xe0, 0x12, 0xb5, 0x60, 0x6d, 0xc3, 0xf7, 0xe8, 0x1a, + 0xf7, 0x75, 0x36, 0x7c, 0x97, 0xfa, 0x1d, 0x9a, 0xf8, 0xb5, 0xd0, 0x50, 0x7d, 0x97, 0x28, 0x01, + 0x53, 0xd2, 0xe2, 0xe9, 0x8b, 0xd6, 0xf2, 0xcd, 0x78, 0x36, 0xbe, 0xbc, 0xf2, 0xf5, 0x9e, 0x9e, + 0x01, 0x99, 0xaa, 0x21, 0x49, 0xc7, 0xa1, 0xa2, 0x7d, 0x3e, 0x52, 0x28, 0x7f, 0x06, 0x80, 0x61, + 0x03, 0x16, 0xbb, 0xa5, 0xe2, 0xb6, 0xe0, 0x04, 0xea, 0xf5, 0x81, 0x82, 0x27, 0xe1, 0x95, 0xaf, + 0xd7, 0xa7, 0x70, 0xa5, 0xaa, 0x3a, 0x45, 0x11, 0x24, 0x5d, 0xb7, 0x96, 0x7e, 0x58, 0x22, 0x24, + 0x43, 0xaf, 0x2f, 0x1c, 0xd0, 0x40, 0x5f, 0x2b, 0x19, 0x1f, 0x70, 0xa6, 0xba, 0xe7, 0xe1, 0x22, + 0x74, 0x6e, 0xac, 0x26, 0xa0, 0xbc, 0xb9, 0x3a, 0x40, 0xd5, 0xe9, 0x6f, 0x03, 0x03, 0x36, 0xfa, + 0x2e, 0xbb, 0xe5, 0x67, 0xfd, 0xf2, 0xde, 0x56, 0x8d, 0xeb, 0x03, 0x4f, 0x38, 0x3f, 0x30, 0x18, + 0x8b, 0x00, 0xed, 0xcc, 0x1d, 0x63, 0x20, 0x1d, 0xc9, 0x9a, 0xfb, 0x9c, 0xd1, 0x8a, 0xb3, 0x41, + 0x19, 0x3a, 0x5b, 0x47, 0x7c, 0x2e, 0x12, 0x10, 0xf6, 0x03, 0xe4, 0x36, 0x14, 0xa9, 0x10, 0x68, + 0xeb, 0xd8, 0x72, 0x96, 0xe1, 0x7c, 0x8e, 0xbb, 0x39, 0xed, 0x87, 0x1f, 0xcc, 0xfb, 0xc5, 0x7f, + 0x5b, 0x9f, 0xbb, 0xfe, 0xf4, 0x59, 0xf7, 0x20, 0x11, 0x43, 0x82, 0xf9, 0x6b, 0x5f, 0x08, 0x20, + 0x17, 0xad, 0x25, 0xb3, 0x75, 0xf3, 0xfa, 0x06, 0x60, 0xd8, 0xd1, 0x1f, 0x0c, 0x87, 0xb7, 0xaa, + 0xa2, 0x25, 0x28, 0xfd, 0x47, 0x6e, 0x73, 0x67, 0x9e, 0x4c, 0xed, 0x48, 0xaa, 0xea, 0xed, 0xea, + 0x82, 0x50, 0xc5, 0xd8, 0xa9, 0x81, 0x0a, 0xaa, 0x93, 0x56, 0xa8, 0x4d, 0x3b, 0x46, 0xd0, 0x4a, + 0x9f, 0xa0, 0xc5, 0x12, 0x64, 0x57, 0xd7, 0xe2, 0xeb, 0xcb, 0x8f, 0xff, 0x3f, 0x13, 0xce, 0x06, + 0x1d, 0xde, 0xdc, 0xb0, 0xf5, 0xb4, 0x0e, 0xa8, 0x15, 0x2d, 0xa6, 0x67, 0x5d, 0xf7, 0x0c, 0x25, + 0x1a, 0x81, 0x8a, 0x1b, 0x30, 0xec, 0xb4, 0xc4, 0x7b, 0x2d, 0x1d, 0x44, 0xb4, 0x43, 0x69, 0xe7, + 0xcd, 0x9d, 0xd7, 0x3e, 0x63, 0x2f, 0x7c, 0x86, 0xad, 0xce, 0xfd, 0xcb, 0xcd, 0xe9, 0x4a, 0xc6, + 0xc1, 0x64, 0x7a, 0xae, 0x36, 0x5b, 0x97, 0x65, 0xde, 0x43, 0x7e, 0xd7, 0x8a, 0x6d, 0xb8, 0x5a, + 0x2d, 0xcf, 0x8e, 0x9e, 0x0a, 0x75, 0xb5, 0x4b, 0x25, 0x51, 0x4b, 0xc3, 0xbd, 0xed, 0x5d, 0xad, + 0x23, 0xee, 0x48, 0x5a, 0x04, 0xda, 0x8f, 0xfc, 0x00, 0x82, 0xfa, 0xae, 0xca, 0x42, 0x86, 0xb6, + 0xea, 0x66, 0x49, 0x1b, 0xd0, 0xde, 0x72, 0x84, 0xd6, 0xb6, 0x13, 0x70, 0x7d, 0xe4, 0x19, 0xb6, + 0xbc, 0xdf, 0xbc, 0x9b, 0xf9, 0x9e, 0xde, 0x51, 0x3d, 0x59, 0xa1, 0xa9, 0xdc, 0xb6, 0xcf, 0x3c, + 0x37, 0x3f, 0xbb, 0xd5, 0x59, 0xa0, 0x91, 0x5b, 0x67, 0xeb, 0xa8, 0x0a, 0xf6, 0x8c, 0x84, 0x02, + 0x44, 0x0d, 0x34, 0xb6, 0xc4, 0xe3, 0xbf, 0x4d, 0xdc, 0xe9, 0xf9, 0x7b, 0x3a, 0x0b, 0x79, 0x86, + 0xed, 0xf5, 0xe1, 0x9d, 0x79, 0x51, 0xe2, 0x09, 0xf1, 0xe2, 0xb5, 0xd4, 0xed, 0xd9, 0x9a, 0xfc, + 0x54, 0xe5, 0xd1, 0x84, 0xb6, 0xf5, 0xb7, 0x8e, 0x8f, 0x3b, 0x53, 0xc7, 0xa3, 0x4e, 0x50, 0x5d, + 0x9a, 0x56, 0xb3, 0x44, 0x7b, 0xf6, 0xd9, 0x6b, 0x89, 0xba, 0xfa, 0x79, 0xae, 0x10, 0x95, 0x00, + 0x6e, 0x71, 0x4f, 0xd7, 0x9c, 0x47, 0xfb, 0xfe, 0xdf, 0x1d, 0x4f, 0xc5, 0x60, 0x6e, 0x8b, 0x1b, + 0xe4, 0xa6, 0x54, 0xbe, 0x5b, 0x9a, 0x71, 0x59, 0xfc, 0x7a, 0x34, 0x27, 0x85, 0x8f, 0xe0, 0xd2, + 0xb8, 0x28, 0xc0, 0xad, 0x4b, 0xbb, 0xb4, 0xa7, 0xf0, 0x68, 0xf6, 0x78, 0x27, 0x5d, 0xb7, 0xd6, + 0x5e, 0x75, 0x6e, 0xfc, 0x37, 0x0d, 0x74, 0x67, 0xfd, 0xad, 0x18, 0x1f, 0x79, 0xa6, 0xfa, 0xb1, + 0x67, 0xd1, 0xd4, 0xa5, 0xd9, 0xd5, 0x9d, 0x24, 0x63, 0xbf, 0xa5, 0xde, 0xb2, 0x13, 0xa1, 0x00, + 0x88, 0x9a, 0x20, 0xf6, 0x53, 0xdc, 0xb8, 0x15, 0xea, 0x45, 0x68, 0x79, 0xb8, 0xe2, 0x3a, 0x49, + 0x8c, 0x34, 0xc6, 0x94, 0xe9, 0x9f, 0xc5, 0x56, 0xc6, 0x1c, 0xc7, 0x3d, 0xda, 0x79, 0x04, 0x5b, + 0xf3, 0xeb, 0xad, 0x14, 0x8a, 0x26, 0x2a, 0xa3, 0x17, 0x80, 0xa2, 0x1d, 0xaa, 0x81, 0x36, 0xa1, + 0x2e, 0x2c, 0xfc, 0x43, 0x2e, 0xc1, 0x8b, 0xff, 0xec, 0x5c, 0xbc, 0x56, 0xaf, 0x6c, 0xba, 0x61, + 0xe9, 0x6f, 0x3d, 0x4d, 0xd1, 0xc2, 0xc3, 0x9c, 0x19, 0x09, 0x6d, 0xb3, 0x5e, 0x08, 0x1f, 0x7b, + 0x84, 0xaa, 0x71, 0xcd, 0xa8, 0x06, 0x16, 0x18, 0x48, 0x07, 0x3e, 0x11, 0x8e, 0x33, 0x1a, 0x13, + 0x39, 0xfe, 0xd7, 0xa2, 0x77, 0x4a, 0x2c, 0x05, 0x26, 0x9a, 0x04, 0x2d, 0xc5, 0xfe, 0xf3, 0xc0, + 0x8e, 0xfa, 0x71, 0xb1, 0xd0, 0xf1, 0xe4, 0x3a, 0x75, 0x8e, 0x3b, 0x27, 0x23, 0xb6, 0x3e, 0xf7, + 0x98, 0x53, 0xe7, 0x3f, 0x9e, 0xae, 0x96, 0x64, 0xcc, 0x1f, 0xdd, 0xa5, 0x8d, 0xb0, 0x25, 0x2d, + 0x05, 0x3d, 0xf2, 0xc7, 0xd3, 0xfc, 0x2c, 0x8f, 0xf0, 0x21, 0x25, 0x2d, 0x05, 0x26, 0xe4, 0xda, + 0x58, 0xe3, 0xdb, 0x1d, 0x61, 0x5b, 0x37, 0xff, 0x55, 0x74, 0x25, 0x12, 0xc2, 0xd3, 0xd6, 0xe6, + 0x0f, 0x35, 0x8f, 0x66, 0x8c, 0x65, 0x42, 0xc3, 0x91, 0xbb, 0x4b, 0x13, 0xe1, 0x92, 0x04, 0xa3, + 0x82, 0x74, 0x68, 0x82, 0x51, 0x1b, 0x55, 0x4d, 0x71, 0x69, 0xa7, 0xb2, 0x82, 0xb8, 0x4b, 0x73, + 0x97, 0x76, 0xeb, 0x69, 0x55, 0x45, 0x43, 0xe8, 0x3f, 0xf2, 0x7e, 0x9e, 0x42, 0x29, 0x7f, 0x5f, + 0x6e, 0x0c, 0x9a, 0x30, 0x08, 0x7b, 0xea, 0x18, 0x24, 0xda, 0xb0, 0x2b, 0x63, 0x3c, 0xb9, 0x4e, + 0x66, 0x5d, 0xcf, 0x3a, 0xe7, 0x6a, 0xd7, 0x39, 0x8e, 0xaf, 0x75, 0x9e, 0x8d, 0xbd, 0xdc, 0xa8, + 0x89, 0x76, 0x75, 0x8c, 0x1a, 0x8d, 0x70, 0x2e, 0xe8, 0xd2, 0xd2, 0x95, 0x6b, 0x0d, 0x97, 0x96, + 0x5b, 0x5d, 0x1a, 0xcd, 0x15, 0x53, 0x0b, 0x18, 0x19, 0x3a, 0x5b, 0xc7, 0x03, 0xce, 0xd4, 0x95, + 0xdc, 0x53, 0xb8, 0x13, 0x18, 0xb6, 0x1f, 0xaf, 0x1d, 0x2d, 0xc7, 0xab, 0x63, 0xb1, 0x6f, 0xcf, + 0xb2, 0x31, 0xdd, 0xd7, 0x8a, 0x76, 0x97, 0xe6, 0x0e, 0xcd, 0x71, 0x8f, 0x76, 0x2a, 0xc1, 0x0c, + 0x0c, 0x1d, 0x2d, 0xef, 0x6c, 0x01, 0xfe, 0x78, 0x20, 0x48, 0x7c, 0xeb, 0x32, 0xc0, 0xda, 0xa4, + 0x23, 0xeb, 0x84, 0x86, 0x62, 0x9f, 0x44, 0x33, 0xae, 0xf8, 0x0e, 0x4f, 0x51, 0xab, 0x2e, 0x2d, + 0x63, 0x9f, 0x01, 0xd8, 0xc4, 0x6a, 0x55, 0x16, 0x4e, 0x22, 0xbd, 0xad, 0xe3, 0x81, 0x67, 0xca, + 0x67, 0xd1, 0xce, 0x5c, 0x89, 0x48, 0xe1, 0xc1, 0x2e, 0x0d, 0xab, 0x3b, 0x34, 0xa7, 0x9f, 0x9b, + 0xf1, 0x8c, 0x8d, 0xce, 0xd6, 0x11, 0xd7, 0xc7, 0x25, 0x4d, 0x6f, 0xcc, 0xea, 0xb7, 0x69, 0x8d, + 0x8b, 0xa3, 0xab, 0x1b, 0x34, 0x7d, 0x7a, 0x03, 0xdf, 0x1f, 0x54, 0x0b, 0x44, 0x44, 0x20, 0x15, + 0x20, 0xec, 0xd7, 0xb2, 0xb2, 0xff, 0x33, 0xa8, 0x4b, 0xe3, 0xbb, 0xc1, 0x52, 0x4d, 0x7c, 0xdd, + 0x5a, 0xc6, 0x38, 0x75, 0xd3, 0xc2, 0xfd, 0xdc, 0x6f, 0x1d, 0x1f, 0x7b, 0xa6, 0x7a, 0x5c, 0x7b, + 0x80, 0x63, 0x13, 0x0a, 0xe2, 0xdb, 0x6d, 0xf2, 0x44, 0x41, 0x91, 0x18, 0x85, 0xae, 0x8a, 0x19, + 0x36, 0xcd, 0xf6, 0x5d, 0xab, 0xfb, 0x33, 0x67, 0xdb, 0x19, 0x30, 0x2e, 0x7c, 0xe8, 0xac, 0xd5, + 0xaa, 0xb0, 0xb5, 0x4f, 0xd1, 0xe6, 0x86, 0x5c, 0xc6, 0x7b, 0xe2, 0x92, 0xd6, 0x0c, 0xd5, 0xcb, + 0x2f, 0x43, 0x7b, 0xf8, 0x64, 0x67, 0x02, 0x67, 0x6a, 0xc2, 0x03, 0x6a, 0x13, 0x83, 0xbb, 0xb5, + 0x09, 0x11, 0x11, 0x09, 0xa9, 0xfb, 0x33, 0x28, 0xb9, 0xd5, 0xa5, 0x01, 0x37, 0x0c, 0x68, 0x84, + 0x4d, 0xdb, 0xc1, 0x2b, 0x03, 0x99, 0xef, 0xf6, 0x91, 0x7f, 0xe8, 0xfb, 0x44, 0x9d, 0xa9, 0x3f, + 0xd6, 0xbf, 0x07, 0xe7, 0x79, 0x51, 0x47, 0xa6, 0xdc, 0x7e, 0xe6, 0x1e, 0xd0, 0x3a, 0xc2, 0x71, + 0x9c, 0x4b, 0xde, 0xf7, 0x64, 0xd7, 0xd3, 0xc7, 0x05, 0x94, 0x33, 0xef, 0x7b, 0xba, 0x01, 0x3d, + 0x0e, 0x0d, 0x95, 0x86, 0x6d, 0x30, 0x18, 0xdf, 0x75, 0x1d, 0xd2, 0x71, 0x1c, 0x67, 0x78, 0x60, + 0xeb, 0x88, 0x26, 0x66, 0x38, 0x8e, 0xf3, 0x56, 0x22, 0x0a, 0x42, 0xb5, 0x75, 0x7c, 0xad, 0x05, + 0xab, 0x8e, 0xff, 0x0c, 0x33, 0xb2, 0x6c, 0x41, 0xa9, 0xcd, 0xd5, 0x22, 0x13, 0xfa, 0x0d, 0x53, + 0x70, 0xe4, 0x58, 0x45, 0xcb, 0x00, 0xd2, 0xdb, 0x8f, 0xdc, 0x29, 0xe1, 0xd1, 0x09, 0xd2, 0xdf, + 0xa1, 0x38, 0x8e, 0xdf, 0x33, 0x30, 0xe7, 0xcf, 0xae, 0x61, 0x45, 0xff, 0x1d, 0x80, 0xf6, 0x04, + 0xb1, 0x13, 0x8a, 0x76, 0x38, 0x67, 0x90, 0x64, 0xeb, 0xe8, 0xda, 0x16, 0x8e, 0xfd, 0x0c, 0x59, + 0xb7, 0x2c, 0x6e, 0x10, 0xfa, 0x9a, 0xfa, 0xaf, 0xde, 0xc8, 0xe6, 0x91, 0xfb, 0xa5, 0x1b, 0x25, + 0x96, 0x08, 0x90, 0x50, 0x42, 0x7b, 0x42, 0xc4, 0x91, 0x09, 0xd3, 0x76, 0x69, 0x87, 0xe3, 0xb8, + 0x47, 0x33, 0x34, 0x39, 0xe8, 0x96, 0x53, 0xcb, 0x48, 0xa9, 0xbf, 0xec, 0x48, 0x50, 0xea, 0x98, + 0x10, 0xdb, 0x25, 0x0a, 0xa7, 0x1d, 0xce, 0x69, 0x04, 0xd9, 0x3a, 0xb6, 0xb6, 0x81, 0xfe, 0xcf, + 0xd0, 0xcf, 0xd0, 0xdf, 0xe4, 0x9c, 0x4c, 0x99, 0x4b, 0x5f, 0xa2, 0xa0, 0x10, 0x26, 0xf8, 0xf6, + 0x1d, 0x36, 0x94, 0x52, 0xee, 0x74, 0xfc, 0xd5, 0xce, 0x7d, 0x25, 0x35, 0x0c, 0x04, 0x51, 0x14, + 0x1d, 0xad, 0xf1, 0xae, 0x73, 0xd6, 0xa7, 0x5f, 0x7e, 0x00, 0x23, 0x39, 0x36, 0xd5, 0xcf, 0xf1, + 0x1e, 0x32, 0xd8, 0x0a, 0x8f, 0xc9, 0x53, 0x65, 0xb5, 0x90, 0xb4, 0xe6, 0xff, 0xb0, 0x5e, 0x3f, + 0xb9, 0x24, 0x49, 0x92, 0x24, 0x65, 0xb8, 0xf4, 0x26, 0x11, 0xd9, 0x80, 0x37, 0x40, 0x83, 0x95, + 0x1e, 0x00, 0x0e, 0x9f, 0xf5, 0xff, 0x00, 0xaf, 0xff, 0xde, 0x31, 0x9a, 0x94, 0xc6, 0xcf, 0x20, + 0x82, 0xce, 0xc1, 0x84, 0x01, 0x1a, 0xac, 0xf4, 0x00, 0xb0, 0xf9, 0xbe, 0x87, 0x01, 0x6e, 0xb3, + 0x84, 0xa7, 0x1b, 0xa3, 0x31, 0xde, 0x92, 0x34, 0xc7, 0x01, 0x17, 0xfe, 0xcc, 0xa8, 0x30, 0xc0, + 0xed, 0x78, 0xed, 0x31, 0xd7, 0xb4, 0x25, 0x09, 0xde, 0x66, 0xfc, 0x26, 0xd7, 0xd1, 0x24, 0xec, + 0xf6, 0x6d, 0xd1, 0x0e, 0x08, 0x17, 0x04, 0xba, 0x4a, 0x5a, 0xfe, 0x52, 0xf3, 0x77, 0x92, 0x17, + 0xbf, 0x3b, 0xcb, 0x55, 0x9d, 0xe5, 0xaa, 0x6e, 0x09, 0xc5, 0xc3, 0x1c, 0x5b, 0xb4, 0x1c, 0xbd, + 0x70, 0x3c, 0x66, 0xe1, 0xcf, 0xa9, 0x4b, 0xd9, 0x62, 0xf6, 0x55, 0x8a, 0xed, 0x13, 0xa3, 0x4f, + 0x65, 0x6e, 0xee, 0xb1, 0x70, 0x04, 0xcb, 0x55, 0x99, 0xe5, 0xea, 0x46, 0x22, 0x70, 0x8c, 0x30, + 0xb8, 0x53, 0x06, 0xc0, 0x90, 0xe5, 0xea, 0xd3, 0xca, 0x15, 0xd5, 0x94, 0x00, 0x12, 0x41, 0x13, + 0x88, 0x8c, 0xed, 0x05, 0xbc, 0x70, 0x45, 0x87, 0xe7, 0xaf, 0xce, 0x96, 0xab, 0x10, 0xcb, 0xd5, + 0xb2, 0x9f, 0x17, 0x30, 0x66, 0x79, 0x1e, 0xe1, 0xcb, 0xdd, 0xc6, 0x46, 0xce, 0x9b, 0xbc, 0xf3, + 0x89, 0x31, 0x66, 0xd3, 0x7f, 0xd4, 0x72, 0xd5, 0xca, 0x72, 0xf5, 0xba, 0x24, 0x49, 0x92, 0xf4, + 0x05, 0x52, 0x0d, 0x4e, 0x68, 0xa4, 0x9a, 0xa9, 0x29, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, + 0x44, 0xae, 0x42, 0x60, 0x82 +}; \ No newline at end of file diff --git a/programs/games/dino/trex.c b/programs/games/dino/trex.c new file mode 100644 index 000000000..e1bcc80cb --- /dev/null +++ b/programs/games/dino/trex.c @@ -0,0 +1,219 @@ +#include "trex.h" + +Trex trex; + +CollisionBox trexDuckingCollisionBox = {.x = 1, .y = 18, .width = 55, .height = 25}; +CollisionBox trexRunningCollisionBox[6] = +{ + {.x = 22, .y = 0, .width = 17, .height = 16}, + {.x = 1, .y = 18, .width = 30, .height = 9}, + {.x = 10, .y = 35, .width = 14, .height = 8}, + {.x = 1, .y = 24, .width = 29, .height = 5}, + {.x = 5, .y = 30, .width = 21, .height = 4}, + {.x = 9, .y = 34, .width = 15, .height = 4} +}; + +TrexAnimFramesEntry trexAnimFrames[5] = { + {.frameCount = 2, .frames = {44, 0}, .msPerFrame = 1000./3}, + {.frameCount = 2, .frames = {88, 132}, .msPerFrame = 1000./12}, + {.frameCount = 1, .frames = {220}, .msPerFrame = 1000./60}, + {.frameCount = 1, .frames = {0}, .msPerFrame = 1000./60}, + {.frameCount = 2, .frames = {264, 323}, .msPerFrame = 1000./8} +}; + +// T - rex player initaliser +// Sets the t - rex to blink at random intervals +void trexInit() { + trex.xPos = 0; + trex.currentFrame = 0; + //this.currentAnimFrames = []; + trex.blinkDelay = 0; + trex.blinkCount = 0; + trex.animStartTime = 0; + trex.timer = 0; + trex.msPerFrame = 1000. / FPS; + trex.status = TREX_STATUS_WAITING; + + trex.jumping = false; + trex.ducking = false; + trex.jumpVelocity = 0; + trex.reachedMinHeight = false; + trex.speedDrop = false; + trex.jumpCount = 0; + trex.jumpspotX = 0; + + trex.groundYPos = RUNNER_DEFAULT_HEIGHT - TREX_HEIGHT - RUNNER_BOTTOM_PAD; + trex.yPos = trex.groundYPos; + trex.minJumpHeight = trex.groundYPos - TREX_MIN_JUMP_HEIGHT; + trex.playingIntro = false; + + trexDraw(0, 0); + trexUpdate(0, TREX_STATUS_WAITING); +} + +// Set the animation status +void trexUpdate(int deltaTime, int opt_status) { + //printf("trex.status = %d\n", trex.status); + trex.timer += deltaTime; + // Update the status + if (opt_status != -1) { + trex.status = opt_status; + trex.currentFrame = 0; + trex.msPerFrame = trexAnimFrames[opt_status].msPerFrame; + trex.currentAnimFrames = trexAnimFrames[opt_status]; + if (opt_status == TREX_STATUS_WAITING) { + trex.animStartTime = getTimeStamp(); + trexSetBlinkDelay(); + } + } + // Game intro animation, T-rex moves in from the left. + if (trex.playingIntro) { + if (trex.xPos < TREX_START_X_POS) { + //printf("trex.xPos = %d\n", trex.xPos); + trex.xPos += max((int)round(((double)TREX_START_X_POS / TREX_INTRO_DURATION) * deltaTime), 1); + } + else { + runnerStartGame(); + } + } + + if (trex.status == TREX_STATUS_WAITING) { + trexBlink(getTimeStamp()); + } + else { + // printf("trex.status = %d\n", trex.status); + trexDraw(trex.currentAnimFrames.frames[trex.currentFrame], 0); + } + + // Update the frame position. + if (trex.timer >= trex.msPerFrame) { + trex.currentFrame = trex.currentFrame == trex.currentAnimFrames.frameCount - 1 ? 0 : trex.currentFrame + 1; + trex.timer = 0; + } + + // Speed drop becomes duck if the down key is still being pressed. + if (trex.speedDrop && trex.yPos == trex.groundYPos) { + trex.speedDrop = false; + trexSetDuck(true); + } +} + +void trexDraw(int x, int y) { + //printf("trexDraw();\n"); + int sourceWidth = trex.ducking && trex.status != TREX_STATUS_CRASHED ? TREX_WIDTH_DUCK : TREX_WIDTH; + int sourceHeight = TREX_HEIGHT; + // Adjustments for sprite sheet position. + int sourceX = x + ATLAS_TREX_X; + int sourceY = y + ATLAS_TREX_Y; + + // Ducking. + if (trex.ducking && trex.status != TREX_STATUS_CRASHED) { + graphicsBlitAtlasImage(sourceX, sourceY, trex.xPos, trex.yPos, sourceWidth, sourceHeight, false); + } + else { + // Crashed whilst ducking. Trex is standing up so needs adjustment. + if (trex.ducking && trex.status == TREX_STATUS_CRASHED) { + trex.xPos++; + } + // Standing / running + graphicsBlitAtlasImage(sourceX, sourceY, trex.xPos, trex.yPos, sourceWidth, sourceHeight, false); + } +} + +void trexSetBlinkDelay() { + trex.blinkDelay = (int)ceil(((double)rand()/RAND_MAX)*TREX_BLINK_TIMING); +} + +void trexBlink(int time) { + //printf("trexBlink(%d)\n", time); + int deltaTime = time - trex.animStartTime; + if (deltaTime < 0) { + deltaTime = DELTA_MS_DEFAULT; + } + if (deltaTime >= trex.blinkDelay) { + trexDraw(trex.currentAnimFrames.frames[trex.currentFrame], 0); + if (trex.currentFrame == 1) { + // Set new random delay to blink. + trexSetBlinkDelay(); + trex.animStartTime = time; + trex.blinkCount++; + } + } +} + +// Initialise a jump +void trexStartJump(double speed) { + if (!trex.jumping) { + trexUpdate(0, TREX_STATUS_JUMPING); + // Tweak the jump velocity based on the speed + trex.jumpVelocity = TREX_INITIAL_JUMP_VELOCITY - (speed / 10); + trex.jumping = true; + trex.reachedMinHeight = false; + trex.speedDrop = false; + } +} + +// Jump is complete, falling down +void trexEndJump() { + if (trex.reachedMinHeight && trex.jumpVelocity < TREX_DROP_VELOCITY) { + trex.jumpVelocity = TREX_DROP_VELOCITY; + } +} + +// Update frame for a jump +void trexUpdateJump(int deltaTime) { + double msPerFrame = trexAnimFrames[trex.status].msPerFrame; + double framesElapsed = deltaTime / msPerFrame; + + // Speed drop makes Trex fall faster. + if (trex.speedDrop) { + trex.yPos += (int)round(trex.jumpVelocity * TREX_SPEED_DROP_COEFFICIENT * framesElapsed); + } + else { + trex.yPos += (int)round(trex.jumpVelocity * framesElapsed); + } + trex.jumpVelocity += TREX_GRAVITY * framesElapsed; + // Minimum height has been reached. + if (trex.yPos < trex.minJumpHeight || trex.speedDrop) { + trex.reachedMinHeight = true; + } + // Reached max height + if (trex.yPos < TREX_MAX_JUMP_HEIGHT || trex.speedDrop) { + trexEndJump(); + } + // Back down at ground level. Jump completed. + if (trex.yPos > trex.groundYPos) { + trexReset(); + trex.jumpCount++; + } + trexUpdate(deltaTime, -1); +} + +// Set the speed drop.Immediately cancels the current jump +void trexSetSpeedDrop() { + trex.speedDrop = true; + trex.jumpVelocity = 1; +} + +void trexSetDuck(bool isDucking) { + if (isDucking && trex.status != TREX_STATUS_DUCKING) { + trexUpdate(0, TREX_STATUS_DUCKING); + trex.ducking = true; + } + else if (trex.status == TREX_STATUS_DUCKING) { + trexUpdate(0, TREX_STATUS_RUNNING); + trex.ducking = false; + } +} + +// Reset the t-rex to running at start of game +void trexReset() { + trex.yPos = trex.groundYPos; + trex.jumpVelocity = 0; + trex.jumping = false; + trex.ducking = false; + trexUpdate(0, TREX_STATUS_RUNNING); + //trex.midair = false; TODO: WTF is midair + trex.speedDrop = false; + trex.jumpCount = 0; +} diff --git a/programs/games/dino/trex.h b/programs/games/dino/trex.h new file mode 100644 index 000000000..0b88612c4 --- /dev/null +++ b/programs/games/dino/trex.h @@ -0,0 +1,83 @@ +#ifndef TREX_H +#define TREX_H + +#include +#include +#include +#include "collisionbox.h" +#include "runner.h" +#include "graphics.h" +#include "misc.h" + +// Blinking coefficient +#define TREX_BLINK_TIMING 7000 + +#define TREX_DROP_VELOCITY -5 +#define TREX_GRAVITY 0.6 +#define TREX_HEIGHT 47 +#define TREX_HEIGHT_DUCK 25 +#define TREX_INITIAL_JUMP_VELOCITY -10 +#define TREX_INTRO_DURATION 750 +#define TREX_MAX_JUMP_HEIGHT 30 +#define TREX_MIN_JUMP_HEIGHT 30 +#define TREX_SPEED_DROP_COEFFICIENT 3 +#define TREX_SPRITE_WIDTH 262 +#define TREX_START_X_POS 25 +#define TREX_WIDTH 44 +#define TREX_WIDTH_DUCK 59 + +// Animation states +typedef enum { + TREX_STATUS_WAITING = 0, + TREX_STATUS_RUNNING = 1, + TREX_STATUS_CRASHED = 2, + TREX_STATUS_JUMPING = 3, + TREX_STATUS_DUCKING = 4, +} TrexStatus; + +typedef struct { + int frameCount; + int frames[2]; + double msPerFrame; +} TrexAnimFramesEntry; + +typedef struct { + int xPos; + int yPos; + int groundYPos; + int currentFrame; + TrexAnimFramesEntry currentAnimFrames; + int blinkDelay; + int blinkCount; + int animStartTime; + int timer; + double msPerFrame; + TrexStatus status; + bool jumping; + bool ducking; + double jumpVelocity; + bool reachedMinHeight; + bool speedDrop; + int jumpCount; + int jumpspotX; + int minJumpHeight; + bool playingIntro; +} Trex; + +extern CollisionBox trexDuckingCollisionBox; +extern CollisionBox trexRunningCollisionBox[6]; +extern Trex trex; + +void trexInit(); +void trexUpdate(int deltaTime, int opt_status); +void trexDraw(int x, int y); +void trexSetBlinkDelay(); +void trexBlink(int time); +void trexStartJump(double speed); +void trexEndJump(); +void trexUpdateJump(int deltaTime); +void trexSetSpeedDrop(); +void trexSetDuck(bool isDucking); +void trexReset(); + +#endif diff --git a/programs/games/dino/ulist.c b/programs/games/dino/ulist.c new file mode 100644 index 000000000..6a578635c --- /dev/null +++ b/programs/games/dino/ulist.c @@ -0,0 +1,241 @@ +#include +#include +#include "ulist.h" + +Ulist* ulist_create() { + Ulist* list = (Ulist*)malloc(sizeof(Ulist)); + if (list == NULL) { + // abort(); + exit(-1); + } + list->head = NULL; + list->tail = NULL; + list->size = 0; + return list; +} + +void ulist_destroy(Ulist* list) { + Node* current = list->head; + Node* next; + + while (current != NULL) { + next = current->next; + free(current); + current = next; + } + + free(list); +} + +void ulist_push_front(Ulist* list, void* data) { + Node* new_node = (Node*)malloc(sizeof(Node)); + if (new_node == NULL) { + // abort(); + exit(-1); + } + new_node->data = data; + new_node->prev = NULL; + new_node->next = list->head; + + if (list->head != NULL) { + list->head->prev = new_node; + } + + list->head = new_node; + + if (list->tail == NULL) { + list->tail = new_node; + } + + list->size++; +} + +void ulist_push_back(Ulist* list, void* data) { + Node* new_node = (Node*)malloc(sizeof(Node)); + if (new_node == NULL) { + // abort(); + exit(-1); + } + new_node->data = data; + new_node->next = NULL; + new_node->prev = list->tail; + + if (list->tail != NULL) { + list->tail->next = new_node; + } + + list->tail = new_node; + + if (list->head == NULL) { + list->head = new_node; + } + + list->size++; +} + +void ulist_remove(Ulist* list, Node* node) { + if (list == NULL || node == NULL) { + return; + } + // Update previous node's next pointer + if (node->prev != NULL) { + node->prev->next = node->next; + } + else { + // If the node is the head, update the head pointer + list->head = node->next; + } + // Update next node's previous pointer + if (node->next != NULL) { + node->next->prev = node->prev; + } + else { + // If the node is the tail, update the tail pointer + list->tail = node->prev; + } + // Free the memory occupied by the node + free(node); + list->size--; +} + +void ulist_remove_front(Ulist* list) { + if (list->head == NULL) { + return; + } + + Node* node_to_remove = list->head; + list->head = list->head->next; + + if (list->head != NULL) { + list->head->prev = NULL; + } + else { + list->tail = NULL; + } + + free(node_to_remove); + list->size--; +} + +void ulist_splice(Ulist* list, int n) { + if (list->size <= n) { + return; // No need to splice if the list size is less than or equal to n + } + int count = list->size - n; + while (count > 0) { + ulist_remove_back(list); + count--; + } +} + +void ulist_remove_back(Ulist* list) { + if (list->tail == NULL) { + return; + } + + Node* node_to_remove = list->tail; + list->tail = list->tail->prev; + + if (list->tail != NULL) { + list->tail->next = NULL; + } + else { + list->head = NULL; + } + + free(node_to_remove); + list->size--; +} + +int ulist_search(Ulist* list, void* data) { + Node* current = list->head; + int index = 0; + + while (current != NULL) { + if (current->data == data) { + return index; + } + + current = current->next; + index++; + } + + return -1; +} + +void* ulist_get_front(Ulist* list) { + if (list->head == NULL) { + return NULL; + } + + return list->head->data; +} + +void* ulist_get_back(Ulist* list) { + if (list->tail == NULL) { + return NULL; + } + + return list->tail->data; +} + +int ulist_size(Ulist* list) { + return list->size; +} + +void ulist_print(Ulist* list) { + Node* current = list->head; + + while (current != NULL) { + printf("%p ", current->data); + current = current->next; + } + + printf("\n"); +} + +void ulist_test() { + // Create a new Ulist + Ulist* list = ulist_create(); + + // Test insertFront + int data1 = 10; + ulist_push_front(list, &data1); + printf("List after inserting 10 at the front: "); + ulist_print(list); // Expected output: 10 + + // Test insertBack + int data2 = 20; + ulist_push_back(list, &data2); + printf("List after inserting 20 at the back: "); + ulist_print(list); // Expected output: 10 20 + + // Test removeFront + ulist_remove_front(list); + printf("List after removing front element: "); + ulist_print(list); // Expected output: 20 + + // Test removeBack + ulist_remove_back(list); + printf("List after removing back element: "); + ulist_print(list); // Expected output: + + // Test search + int data3 = 30; + ulist_push_front(list, &data3); + printf("Index of 30 in the list: %d\n", ulist_search(list, &data3)); // Expected output: 0 + + // Test getFront + int* front = (int*)ulist_get_front(list); + printf("Front element of the list: %d\n", *front); // Expected output: 30 + + // Test getBack + int* back = (int*)ulist_get_back(list); + printf("Back element of the list: %d\n", *back); // Expected output: 30 + + // Test getSize + printf("Size of the list: %d\n", ulist_size(list)); // Expected output: 1 + + // Destroy the list + ulist_destroy(list); +} diff --git a/programs/games/dino/ulist.h b/programs/games/dino/ulist.h new file mode 100644 index 000000000..cdb056bfd --- /dev/null +++ b/programs/games/dino/ulist.h @@ -0,0 +1,31 @@ +#ifndef ULIST_H_ +#define ULIST_H_ + +typedef struct Node { + void* data; + struct Node* prev; + struct Node* next; +} Node; + +typedef struct Ulist { + Node* head; + Node* tail; + int size; +} Ulist; + +Ulist* ulist_create(); +void ulist_destroy(Ulist* list); +void ulist_push_front(Ulist* list, void* data); +void ulist_push_back(Ulist* list, void* data); +void ulist_remove(Ulist *list, Node *node); +void ulist_remove_front(Ulist* list); +void ulist_splice(Ulist* list, int n); +void ulist_remove_back(Ulist* list); +int ulist_search(Ulist* list, void* data); +void* ulist_get_front(Ulist* list); +void* ulist_get_back(Ulist* list); +int ulist_size(Ulist* list); +void ulist_print(Ulist* list); +void ulist_test(); + +#endif /* ULIST_H_ */ \ No newline at end of file