- add arcade mode and arcade-only configuration option

- scoreboard and name entry for arcade mode
- separate save data for quest and arcade modes
This commit is contained in:
ohsqueezy 2023-11-05 00:28:28 -04:00
parent 8d9ec69656
commit 18fb61c236
13 changed files with 1339 additions and 234 deletions

View File

@ -301,7 +301,7 @@ WINDOWS_OBJ := $(addprefix $(WINDOWS_BUILD_DIR)/, glew.o SDL2_rotozoom.o SDL2_gf
Cakefoot-win32.exe: CC = i686-w64-mingw32-gcc-posix
Cakefoot-win32.exe: CXX = i686-w64-mingw32-g++-posix
Cakefoot-win32.exe: CFLAGS = -Wall -Wextra -g -O0 -c -I$(SB_LIB_DIR) -I$(SB_SRC_DIR) -DGLEW_STATIC \
Cakefoot-win32.exe: CFLAGS = -Wall -Wextra -O3 -c -I$(SB_LIB_DIR) -I$(SB_SRC_DIR) -DGLEW_STATIC \
-I$(SDL_MINGW)/include/SDL2 -I$(SDL_IMG_MINGW)/include/SDL2 -I$(SDL_TTF_MINGW)/include/SDL2 -I$(SDL_MIXER_MINGW)/include/SDL2
Cakefoot-win32.exe: CXXFLAGS = $(CFLAGS) --std=c++17
Cakefoot-win32.exe: LFLAGS = -lpthread -lstdc++fs \
@ -314,7 +314,7 @@ Cakefoot-win32.exe: $(WINDOWS_OBJ) config.json
mkdir ${basename $@}
cp $(SDL_MINGW)/bin/*.dll $(SDL_IMG_MINGW)/bin/*.dll $(SDL_TTF_MINGW)/bin/*.dll $(SDL_MIXER_MINGW)/bin/*.dll ${basename $@}
cp /usr/i686-w64-mingw32/lib/libwinpthread-1.dll ${basename $@}
rsync -arR resource/ src/shaders/ config.json ${basename $@}
rsync -arRL resource/ src/shaders/ config.json BPmono.ttf ${basename $@}
cp $(WINDOWS_BUILD_DIR)/$@ ${basename $@}
zip -r ${@:exe=zip} ${basename $@}
mv ${basename $@} /tmp

View File

@ -2,12 +2,13 @@
"display":
{
"dimensions": [864, 486],
"title": "Microsoft Windows 11 Home Edition",
"title": "c a k e f o o t",
"debug": false,
"render driver": "opengl",
"show-cursor": true,
"fluid resize": true,
"fps": false,
"fullscreen": false,
"clear color": [0.0, 0.0, 0.0, 1.0],
"playing field aspect": 1.7777777,
"playing field color": [0.2, 0.2, 0.2, 1.0],
@ -24,7 +25,33 @@
"level hud foreground": [255.0, 255.0, 255.0, 255.0],
"level hud background": [0.0, 0.0, 0.0, 60.0],
"hitbox": false,
"use play button": true
"use play button": false,
"arcade only": false,
"use arcade prompt": false,
"game over text": "GAME OVER",
"game over display time": 2.5,
"game over foreground": [255.0, 255.0, 255.0, 255.0],
"game over background": [0.0, 0.0, 0.0, 255.0],
"game over translation": [0.0, 0.0],
"game over scale": [0.25, 0.05],
"arcade rank scale": [0.4, 0.1],
"arcade rank dimensions": [300.0, 80.0],
"arcade rank translation": [-1.2, 0.5],
"arcade distance scale": [0.4, 0.1],
"arcade distance dimensions": [300.0, 80.0],
"arcade distance translation": [0.0, 0.5],
"arcade time remaining scale": [0.4, 0.1],
"arcade time remaining translation": [1.2, 0.5],
"scoreboard foreground": [155.0, 155.0, 155.0, 255.0],
"scoreboard background": [0.0, 0.0, 0.0, 0.0],
"scoreboard translation": [-0.38, 0.845],
"scoreboard scale": [1.4, 0.15],
"scoreboard translation": [-0.45, 0.865],
"scoreboard scale": [1.35, 0.14],
"qr texture": "resource/qr.png",
"qr translation": [1.49, -0.7],
"qr scale": [0.205, 0.225],
"end screen timeout": 40.0
},
"configuration":
@ -47,7 +74,7 @@
"input":
{
"suppress any key on mods": true,
"any key ignore commands": ["left", "right"]
"any key ignore commands": ["left", "right", "up", "down"]
},
"keys":
@ -136,35 +163,69 @@
"text foreground": [200.0, 200.0, 200.0, 255.0],
"text background": [60.0, 60.0, 60.0, 190.0],
"start text": "PLAY",
"start translation": [0.0, -0.45],
"start translation": [0.0, -0.4],
"start alt texture": "resource/press_button_to_start.png",
"start alt translation": [0.0, -0.5],
"start alt scale": [0.5787, 0.91],
"resume text": "RESUME",
"resume translation": [0.0, 0.25],
"reset text": "RESET",
"reset translation": [0.0, -0.25],
"level decrement translation": [-0.65, -0.675],
"level decrement translation": [-0.67, -0.6],
"level decrement text": "<",
"level decrement dimensions": [40.0, 40.0],
"level increment translation": [0.65, -0.675],
"level increment translation": [0.67, -0.6],
"level increment text": ">",
"level select translation": [0.0, -0.675],
"level select dimensions": [350.0, 28.0],
"level select scale": [0.6, 0.06],
"level select translation": [0.0, -0.6],
"level select dimensions": [350.0, 22.0],
"level select scale": [0.63, 0.04],
"level select text": "LEVEL ",
"profile translation": [0.0, -0.83],
"profile dimensions": [320.0, 28.0],
"profile scale": [0.6, 0.06],
"profile translation": [0.0, -0.71],
"profile dimensions": [320.0, 22.0],
"profile scale": [0.63, 0.04],
"profile text": "DIFFICULTY: ",
"profile decrement text": "<",
"profile decrement translation": [-0.65, -0.83],
"profile decrement translation": [-0.67, -0.71],
"profile increment text": ">",
"profile increment translation": [0.65, -0.83],
"profile increment translation": [0.67, -0.71],
"challenge text": "CHALLENGE: ",
"challenge translation": [0.0, -0.82],
"challenge dimensions": [320.0, 22.0],
"challenge scale": [0.63, 0.04],
"challenge decrement text": "<",
"challenge decrement translation": [-0.67, -0.82],
"challenge increment text": ">",
"challenge increment translation": [0.67, -0.82],
"view text": "VIEW: ",
"view translation": [0.0, -0.925],
"view dimensions": [320.0, 22.0],
"view scale": [0.63, 0.04],
"view decrement text": "<",
"view decrement translation": [-0.67, -0.925],
"view increment text": ">",
"view increment translation": [0.67, -0.925],
"volume on texture": "resource/vol.png",
"volume off texture": "resource/vol_off.png",
"volume translation": [-1.65, -0.85],
"volume scale": 0.08,
"play texture": "resource/Play_Button.png",
"play translation": [0.0, 0.0],
"play scale": 0.6
"play scale": 0.6,
"name":
{
"arrow dimensions": [80.0, 40.0],
"arrow scale": [0.3, 0.15],
"arrow increment y": -0.25,
"arrow decrement y": -0.76,
"character 1 x": -0.4,
"character 2 x": 0.0,
"character 3 x": 0.4,
"arrow increment texture": "resource/up_arrow.png",
"arrow decrement texture": "resource/down_arrow.png",
"character dimensions": [60.0, 80.0],
"character scale": [0.2, 0.15],
"character y": -0.5
}
},
"world": [
@ -186,7 +247,46 @@
}
],
"audio": {
"challenge": [
{
"name": "RESUME QUEST"
},
{
"name": "NEW QUEST"
},
{
"name": "LEVEL SELECT"
},
{
"name": "RESUME ARCADE",
"time limit": 30.0,
"checkpoint addition": 10.0,
"level addition": 20.0,
"bank bonus": 5.0
},
{
"name": "ARCADE",
"time limit": 30.0,
"checkpoint addition": 10.0,
"level addition": 20.0,
"bank bonus": 5.0
}
],
"view": [
{
"name": "ORIGINAL"
},
{
"name": "MIRROR"
},
{
"name": "WARPED"
}
],
"audio":
{
"files":
{
"restart": "resource/no.ogg",

View File

@ -1,22 +1,23 @@
{
"display":
{
"render driver": "opengles2",
"fluid resize": false
},
"display":
{
"render driver": "opengles2",
"fluid resize": false,
"use play button": true
},
"recording":
{
"enabled": false
},
"recording":
{
"enabled": false
},
"log":
{
"enabled": false
},
"log":
{
"enabled": false
},
"configuration":
{
"auto-refresh": false
}
"configuration":
{
"auto-refresh": false
}
}

View File

@ -126,7 +126,7 @@
return response.json();
}
}).then((response) => {
document.getElementById("session").innerHTML = "Progress is automatically saved to your browswer (session ID: " + response["id"] + ")";
document.getElementById("session").innerHTML = "Progress is automatically saved to your browser (session ID: " + response["id"] + ")";
});
}
};

2
lib/sb

@ -1 +1 @@
Subproject commit e168844fc5310581da43b32ac7ba8b2c2007a6e3
Subproject commit a91bc4b773a0938f4f3984ce78d48d9ba70dec73

View File

@ -957,7 +957,7 @@
{
"curve": [
[80, 244], [80, 244], [784, 244], [784, 244]
[280, 244], [280, 244], [584, 244], [584, 244]
]
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
resource/qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,8 @@
/* >> Cakefoot << */
/* _ _
* c/a`k-e'f`o^o~t-, | a single-button action game | by @ooofoam
* / _< | wow a living cake the sweet | play online: https://shampoo.ooo/cakefoot
* _> `~_/ | taste of victory | open source: https://open.shampoo.ooo/shampoo/cakefoot
*/
#pragma once
@ -14,6 +18,8 @@
#include <memory>
#include <functional>
#include <malloc.h>
#include <chrono>
#include <ctime>
/* Include Game.hpp before any other SDL-related headers because it defines SDL_MAIN_HANDLED */
#include "Game.hpp"
@ -53,6 +59,108 @@
#include "Curve.hpp"
#include "Enemy.hpp"
/*!
* Container for a list of arcade scores that keeps scores sorted as they are added.
*/
class ArcadeScores
{
public:
struct Score
{
float time = 0.0f;
int distance = 0;
std::string name = "";
std::time_t date;
Score(float time, int distance, const std::string& name = "") : time(time), distance(distance), name(name), date(std::time(nullptr)) {};
Score() : Score(0.0f, 0) {};
bool operator >(const Score& other) const
{
return time > other.time || (time == other.time && distance > other.distance);
}
};
private:
std::vector<Score> scores;
public:
void add(const Score& incoming)
{
auto score = scores.begin();
for (; score != scores.end(); score++) if (incoming > *score) break;
scores.insert(score, incoming);
}
int rank(const Score& other) const
{
std::size_t index = 0;
for (; index < scores.size() && scores[index] > other; index++);
return index + 1;
}
nlohmann::json json(const std::string& date_format) const
{
nlohmann::json json;
for (const ArcadeScores::Score& score : scores)
{
std::ostringstream date;
date << std::put_time(std::localtime(&score.date), date_format.c_str());
nlohmann::json entry = {
{"name", score.name},
{"time", score.time},
{"distance", score.distance},
{"date", date.str()}
};
json.push_back(entry);
}
return json;
}
std::string formatted(int rows, int cols) const
{
std::size_t index = 0;
std::string name;
std::ostringstream text;
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < cols; col++)
{
std::ostringstream score;
index = cols * col + row;
if (index >= scores.size())
{
name = "---";
score << "";
}
else
{
name = scores[index].name;
if (scores[index].time > 0.0f)
{
score << std::setprecision(1) << std::fixed << scores[index].time;
}
else
{
score << scores[index].distance << "m";
}
}
text << std::setw(2) << index + 1 << ". " << name << " " << std::setw(6) << score.str() << " ";
if (col == cols - 1)
{
text << std::endl;
}
}
}
return text.str();
}
};
/*!
* The main game object. There is currently only support for one of these to exist at a time.
*
@ -75,12 +183,16 @@ private:
typedef sb::Game super;
/* Static members */
inline static std::string reset_command_name = "reset";
const inline static std::string progress_file_path = "storage/cakefoot_progress.json";
const inline static std::string reset_command_name = "reset";
const inline static fs::path progress_file_path = "storage/cakefoot_progress.json";
const inline static fs::path arcade_scores_file_path = "storage/cakefoot_arcade_scores.json";
const inline static fs::path levels_file_path = "resource/levels.json";
const inline static std::string date_format = "%Y/%m/%d %H:%M";
/* Member vars */
std::shared_ptr<SDL_Cursor> poke, grab;
int previous_frames_per_second = 0, curve_index = 0, curve_byte_count = 0, level_index = 0, level_select_index = 1, profile_index = 0;
int previous_frames_per_second = 0, curve_index = 0, curve_byte_count = 0, level_index = 0, level_select_index = 1, profile_index = 0,
challenge_index = 0, view_index = 0, name_entry_index = 0;
std::map<std::string, GLuint> uniform;
GLuint shader_program;
glm::mat4 view {1.0f}, projection {1.0f};
@ -96,16 +208,36 @@ private:
{"profile increment", sb::Pad<>()},
{"profile decrement", sb::Pad<>()},
{"volume", sb::Pad<>()},
{"play", sb::Pad<>()}
{"play", sb::Pad<>()},
{"challenge increment", sb::Pad<>()},
{"challenge decrement", sb::Pad<>()},
{"view increment", sb::Pad<>()},
{"view decrement", sb::Pad<>()},
{"name 1", sb::Pad<>()},
{"name 1 increment", sb::Pad<>()},
{"name 1 decrement", sb::Pad<>()},
{"name 2", sb::Pad<>()},
{"name 2 increment", sb::Pad<>()},
{"name 2 decrement", sb::Pad<>()},
{"name 3", sb::Pad<>()},
{"name 3 increment", sb::Pad<>()},
{"name 3 decrement", sb::Pad<>()}
};
std::shared_ptr<TTF_Font> large_font {font(configuration()("display", "default font path").get<std::string>(), 72)},
small_font {font(configuration()("display", "default font path").get<std::string>(), 12)};
std::map<std::string, sb::Text> label = {
{"fps", sb::Text(font())},
{"clock", sb::Text(font())},
{"level", sb::Text(font())},
{"level select", sb::Text(font())},
{"profile", sb::Text(font())}
{"profile", sb::Text(font())},
{"challenge", sb::Text(font())},
{"view", sb::Text(font())},
{"game over", sb::Text(font())},
{"arcade rank", sb::Text(large_font)},
{"arcade distance", sb::Text(large_font)}
};
sb::Sprite playing_field, checkpoint_on, checkpoint_off, coin {"resource/coin/coin-0.png", glm::vec2{12.0f / 486.0f}};
sb::Sprite playing_field, checkpoint_on, checkpoint_off, coin {"resource/coin/coin-0.png", glm::vec2{12.0f / 486.0f}}, qr_code;
sb::Timer on_timer, run_timer, unpaused_timer;
glm::vec3 camera_position {0.0f, 0.0f, 2.0f}, subject_position {0.0f, 0.0f, 0.0f};
float zoom = 0.0f;
@ -116,7 +248,11 @@ private:
std::map<std::string, std::shared_ptr<TTF_Font>> fonts;
std::map<std::string, sb::audio::Chunk> audio;
Character character {_configuration, audio};
bool use_play_button = false;
bool use_play_button = false, coin_collected = false;
ArcadeScores arcade_scores;
ArcadeScores::Score arcade_score;
std::string name_entry = "AAA";
sb::Text scoreboard {large_font};
/*!
* Load sound effects and music into objects that can be used by the SDL mixer library. Use chunk objects for background music instead of
@ -140,6 +276,11 @@ private:
*/
void set_up_buttons();
/*!
* Respond to a change in index in the challenge spinner.
*/
void toggle_challenge();
/*!
* Style the HUD elements based on the configuration settings. This can be re-run to apply changes made in the configuration.
*/
@ -151,10 +292,15 @@ private:
void load_vbo();
/*!
* @return the currently active curve
* @return The current level's curve
*/
Curve& curve();
/*!
* @return The current level's curve
*/
const Curve& curve() const;
/*!
* Change the level to the given index. Load enemies, update the curve index.
*
@ -166,12 +312,94 @@ private:
* Save the JSON in the `progress` field of Game::configuration to storage at Cakefoot::progress_file_path.
*
* For PC builds, the folder `storage/` will be created in the current working directory if it doesn't exist already. The user must have
* permission to create folders in the directory. The file `progress.json` will also be created if necessary.
* permission to create folders in the directory. The file `cakefoot_progress.json` will also be created if necessary.
*
* For web builds, Emscripten is used to abstract this function so that writing to a browser's Indexed DB is done automatically. The folder
* `storage/` must have been mounted already using Emscripten's FS module.
*/
void write_progress();
void write_progress() const;
/*!
* Use the same procedure as Cakefoot::write_progress() to write arcade scores to a JSON file in `storage/cakefoot_arcade_scores.json`.
*
* @see Cakefoot::write_progress()
*/
void write_scores() const;
/*!
* @return Sum of the lengths of all curves on all regular levels.
*/
int length() const;
/*!
* @return The character's current distance from the beginning of the first level.
*/
int distance() const;
/*!
* @return The time limit of the current run. Combines the challenge mode's initial limit with added time for completed levels and checkpoints.
*/
float limit() const;
/*!
* @return True if arcade is the current mode
*/
bool arcade() const;
/*!
* @return True if quest is the current mode
*/
bool quest() const;
/*!
* @return True if level select is the current mode
*/
bool level_select() const;
/*!
* Remove coin from the enemy and the level. If quest or arcade mode is active and flag is not set, add the coin to the appropriate bank.
*
* @param add_to_bank flag to either add to the bank or not
*/
void collect_coin(bool add_to_bank = true);
/*!
* Move the level index to the end level, causing the game over state to end.
*/
void end_game_over_display();
/* This animation can be used to end the game over state after the time limit is reached. Play once with a delay to let the game over screen
* display temporarily before being ended by this animation. */
Animation game_over_animation {sb::Animation(std::bind(&Cakefoot::end_game_over_display, this))};
/*!
* Write score, refresh scoreboard, and load the title screen
*/
void submit_score();
/* Can be used to time out the name entry screen */
Animation submit_score_animation {sb::Animation(std::bind(&Cakefoot::submit_score, this))};
/*!
* Get the arcade time as the amount of time remaining before the limit is reached.
*
* @param limit Time limit of arcade mode
* @return Amount of time remaining in seconds
*/
float arcade_time_remaining(float limit) const;
/*!
* Convert an amount of seconds to MMM:SS.sss format. The amount of minute digits will be as many as necessary.
*
* @param amount Amount in seconds to convert
* @return String formatted to MMM:SS.sss
*/
static std::string format_clock(float amount);
/*!
* Build a texture displaying the top 25 scores and assign the texture to the scoreboard sprite.
*/
void refresh_scoreboard();
public:

View File

@ -33,7 +33,10 @@ bool Enemy::collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::ve
void Enemy::take_coin()
{
_coin_taken = true;
if (_coin.has_value())
{
_coin_taken = true;
}
}
bool Enemy::coin_taken() const
@ -43,7 +46,10 @@ bool Enemy::coin_taken() const
void Enemy::collect_coin()
{
coin_collected = true;
if (_coin.has_value())
{
coin_collected = true;
}
}
Slicer::Slicer(const Curve& curve, float relative, float speed, float stray) : curve(curve), relative(relative), speed(speed), stray(stray)
@ -304,7 +310,7 @@ bool Projectile::coinified() const
return _coin.has_value();
}
Projector::Projector(const Character& character, const glm::vec3& position, float speed, float rate, const double& release_delay) :
Projector::Projector(const Character& character, const glm::vec3& position, float speed, float rate, float release_delay) :
character(character), position(position), speed(speed), rate(rate), release_delay(release_delay)
{
animation_charge.frame_length(rate);

View File

@ -293,7 +293,7 @@ private:
const Character& character;
glm::vec3 position = glm::vec3{0.0f};
float speed = 0.0f, rate = 0.0f;
const double& release_delay;
float release_delay;
sb::Sprite sprite;
sb::Animation animation_charge = sb::Animation(std::bind(&Projector::charge, this)),
animation_release = sb::Animation(std::bind(&Projector::release, this));
@ -316,7 +316,7 @@ public:
* @param rate amount of seconds between each projectile
* @param release_delay amount in seconds between charge and release
*/
Projector(const Character& character, const glm::vec3& position, float speed = 0.51440329f, float rate = 3.0f, const double& release_delay = 0.3);
Projector(const Character& character, const glm::vec3& position, float speed = 0.51440329f, float rate = 3.0f, float release_delay = 0.3f);
/*!
*/