Compare commits

...

20 Commits

Author SHA1 Message Date
csd4ni3l
7a8fdd64ba Fix README saying 2 instead of 3 minigames 2025-11-03 15:24:55 +01:00
csd4ni3l
31973ad9d3 fix best time being None for 0.1s which looked bad 2025-11-03 15:23:36 +01:00
csd4ni3l
60bf2a65be Fix music always playing, add more jumpscares and increase chance of jumpscares 2025-11-03 15:15:50 +01:00
csd4ni3l
c616865438 update CREDITS with more attribution 2025-11-02 23:14:03 +01:00
csd4ni3l
d2792d41de Add random jumpscares which you can disable, make settings warning for all settings, remove sfx setting, remove WIP warning 2025-11-02 23:07:23 +01:00
csd4ni3l
0904eede95 Add whack a pumpkin and best score, move games to game_info and setup_game, make a modular basegame template and build upon that, fix game settings not using the current game specific settings 2025-11-02 21:23:09 +01:00
csd4ni3l
7094603bd7 Improve README, add bird enemy to pumpkin roll and finish it, add high scores to profile 2025-11-02 18:02:30 +01:00
csd4ni3l
f056977548 Cut out tombstones, make settings depend on the game, fix default settings values, fix game crashing with no setting cookies, add multiple enemies into pumpkin roll 2025-11-02 16:50:43 +01:00
csd4ni3l
dbaecc846c Add starting Pumpkin roll game which i will improve, add score and high score counters to both games 2025-11-02 13:34:28 +01:00
csd4ni3l
cf70384239 Move static resources into their own directories 2025-11-01 18:13:09 +01:00
csd4ni3l
e7293aeb09 Make game.js more modular, add page reload warning to graphics settings and make them work, add some starting code to pumpkin roll and add it to navbar 2025-11-01 18:02:38 +01:00
csd4ni3l
233ab2f878 Pumpkin memory now works, has a timer, and you can win. No high scores yet. 2025-11-01 14:43:26 +01:00
csd4ni3l
dcd6e02564 fix warning not being displayed correctly 2025-10-29 23:05:14 +01:00
csd4ni3l
b86b49c911 add warning that pumpkin memory is not part of this week 2025-10-29 23:03:39 +01:00
csd4ni3l
9dc0b42a2e Add a texturebutton in UI, make buttons for pumpkin memory and a difficulty selector, no game yet 2025-10-29 23:01:52 +01:00
csd4ni3l
5bc9ace173 I forgot to commit, but yeah i started learning kaplay and made UI for it, and added settings, no game done yet. 2025-10-29 22:18:45 +01:00
csd4ni3l
e08062a732 use New Rocker font instead of Creepster 2025-10-25 18:46:21 +02:00
csd4ni3l
78592cb848 remove logout button in login & register 2025-10-25 18:13:25 +02:00
csd4ni3l
8c334a89e2 remove logout button in countdown if not logged in 2025-10-25 18:11:49 +02:00
csd4ni3l
294c30d7e7 Return incorrect pattern error if pattern is not correct 2025-10-25 18:10:07 +02:00
26 changed files with 1158 additions and 18 deletions

14
CREDITS
View File

@@ -1,3 +1,17 @@
Background Music from Mikhail Smusev from Pixabay
https://pixabay.com/music/scary-childrens-tunes-halloween-background-music-425891/
Jumpscare Sound from Meme-soundboard
https://meme-soundboard.net/jumpscare-soundboard/
FNAF Jumpscare Image from InspiredPencil
https://ar.inspiredpencil.com/pictures-2023/fnaf-2-foxy-jumpscare
Bird image from Google's dino game
Tombstone image from Scary PNGs by Vecteezy
https://www.vecteezy.com/free-png/scary
Huge Thanks to Python for being the programming language used in this app.
https://www.python.org/

View File

@@ -1,4 +1,5 @@
LoginWeen is an app where you login/register with a halloween pumpkin carving as a password.
There are 3 minigames you can play after, or you can check out the countdown to the next Loginween!
Live Demo: https://loginween.csd4ni3l.hu

17
app.py
View File

@@ -121,6 +121,8 @@ def login():
flask_login.login_user(user, remember=True)
return redirect(url_for("main"))
else:
return Response("Incorrect pattern.", 403)
@app.route("/register", methods=["GET", "POST"])
def register():
@@ -249,4 +251,19 @@ def logout():
flask_login.logout_user()
return redirect(url_for("login"))
@app.route("/pumpkin_memory")
@login_required
def pumpkin_memory():
return render_template("pumpkin_memory.jinja2")
@app.route("/pumpkin_roll")
@login_required
def pumpkin_roll():
return render_template("pumpkin_roll.jinja2")
@app.route("/whack_a_pumpkin")
@login_required
def whack_a_pumpkin():
return render_template("whack_a_pumpkin.jinja2")
app.run(host=os.getenv("HOST", "0.0.0.0"), port=int(os.getenv("PORT", 8080)), debug=os.getenv("DEBUG_MODE", False).lower() == "true")

BIN
static/graphics/bird.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

View File

Before

Width:  |  Height:  |  Size: 946 B

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

151
static/js/game.js Normal file
View File

@@ -0,0 +1,151 @@
const WIDTH = 1280;
const HEIGHT = 720;
let music_played = false;
function jumpscare() {
play("jumpscare", {
volume: 1.5
})
const jumpscare_sprite = create_sprite(0, 0, "jumpscare");
setTimeout(() => {
destroy(jumpscare_sprite);
}, 1500);
}
function change_setting(category, setting, value, GAME_TITLE) {
localStorage.setItem(`${GAME_TITLE} ${setting}`, value);
go("settings", category);
}
function show_settings(category, GAME_TITLE, SETTINGS) {
const x = 400;
const label_x = 50;
const space_between = 100;
let y = 130 + space_between;
create_label(label_x, y - space_between, "All settings require a page reload to take effect!", 32);
for (let key in SETTINGS[category]) {
const settings_dict = SETTINGS[category][key];
const currentKey = key;
create_label(label_x, y + 10, key, 32);
let value = localStorage.getItem(`${GAME_TITLE} ${key}`);
if (value == null) {
localStorage.setItem(`${GAME_TITLE} ${key}`, settings_dict.default);
value = settings_dict.default;
}
if (settings_dict.type == "bool") {
horizontal_buttons(x, y, [
[
"ON",
value === "true" ? color(255, 255, 255) : color(127, 127, 127),
color(0, 0, 0, 0),
() => { change_setting(category, currentKey, true, GAME_TITLE); }
],
[
"OFF",
value === "false" ? color(255, 255, 255) : color(127, 127, 127),
color(0, 0, 0, 0),
() => { change_setting(category, currentKey, false, GAME_TITLE); }
]
], 100, 50, 20);
}
else if (settings_dict.type == "option") {
create_dropdown(x, y, 300, 75, settings_dict.options, settings_dict.options.indexOf(value), (option) => {
localStorage.setItem(`${GAME_TITLE} ${currentKey}`, option);
});
}
else if (settings_dict.type == "slider") {
create_slider(x, y, 400, Number(settings_dict.min), Number(settings_dict.max), Number(value), (new_value) => {
localStorage.setItem(`${GAME_TITLE} ${currentKey}`, new_value);
});
}
y = y + space_between;
}
}
function create_start_overlay(GAME_TITLE) {
const overlay = add([
rect(WIDTH, HEIGHT),
color(0, 0, 0),
opacity(0.6),
pos(0, 0),
area(),
z(1000),
"overlay"
])
const text_label = add([
text("Click to Start", { size: 48 }),
pos(WIDTH / 2, HEIGHT / 2),
anchor("center"),
color(255, 255, 255),
z(1001)
])
onClick("overlay", () => {
const bgm = play("music", {
volume: Number(localStorage.getItem(`${GAME_TITLE} Music Volume`) || 50) / 100,
loop: true
});
destroy(overlay);
destroy(text_label);
})
}
function start_game() {
const [GAME_TITLE, SETTINGS] = game_info();
kaplay(
{
width: WIDTH,
height: HEIGHT,
canvas: document.getElementById("canvas"),
root: document.getElementById("game-container"),
crisp: !localStorage.getItem(`${GAME_TITLE} Anti-Aliasing`),
texFilter: (localStorage.getItem(`${GAME_TITLE} Texture Filtering`) || "nearest").toLowerCase(),
maxFPS: Number(localStorage.getItem(`${GAME_TITLE} FPS Limit`)),
font: "New Rocker",
background: "#e18888",
}
);
setup_game();
loadSprite("jumpscare", "/static/graphics/jumpscare.jpg");
loadSound("jumpscare", "/static/sound/jumpscare.mp3");
loadSound("music", "/static/sound/music.mp3");
scene("settings", (setting_category) => {
let generated_button_lists = Object.entries(SETTINGS).map(([key, value]) => [key, color(127, 127, 127), color(0, 0, 0, 0), scene_lambda("settings", key)]);
generated_button_lists = [["Back", color(127, 127, 127), color(0, 0, 0, 0), scene_lambda("main_menu")]].concat(generated_button_lists);
horizontal_buttons(10, 10, generated_button_lists, 200, 75, 10);
if (setting_category != null) {
show_settings(setting_category, GAME_TITLE, SETTINGS);
}
else {
show_settings(Object.keys(SETTINGS)[0], GAME_TITLE, SETTINGS);
}
})
scene("main_menu", () => {
create_label(WIDTH / 2 - 16 * GAME_TITLE.length, HEIGHT / 4, GAME_TITLE, 56);
vertical_buttons(WIDTH / 4, HEIGHT / 2.25, [["Play", color(127, 127, 127), color(0, 0, 0, 0), scene_lambda("play")], ["Settings", color(127, 127, 127), color(0, 0, 0, 0), scene_lambda("settings")]], WIDTH / 2, HEIGHT / 8, HEIGHT / 50)
if (!music_played && localStorage.getItem(`${GAME_TITLE} Music`) == 'true') {
create_start_overlay(GAME_TITLE);
music_played = true;
}
});
go("main_menu");
}

332
static/js/gameui.js Normal file
View File

@@ -0,0 +1,332 @@
function setup_button(button, on_click, label_text, text_color, w, h) {
if (label_text != null) {
button.add([
text(label_text, {
width: w / 1.5,
size: 28,
}),
text_color,
pos(w / 2 - (label_text.length * 8), h / 2 - 18)
]);
}
button.onClick(on_click);
button.onHover(() => {
button.scale = vec2(1.025, 1.025);
setCursor("pointer");
});
button.onHoverEnd(() => {
button.scale = vec2(1, 1);
setCursor("default");
});
return button;
}
function create_button(x, y, w, h, label_text, bg, text_color, on_click) {
let button = add([
rect(w, h),
pos(x, y),
bg,
area(),
]);
return setup_button(button, on_click, label_text, text_color, w, h);
}
function create_texturebutton(x, y, image_name, on_click) {
let button = add([
sprite(image_name),
pos(x, y),
area(),
]);
return setup_button(button, on_click);
}
function create_sprite(x, y, image_name) {
let image_sprite = add([
sprite(image_name),
pos(x, y)
])
return image_sprite;
}
function create_slider(x, y, w, min_val, max_val, initial_val, on_change) {
const slider_height = 15;
const handle_size = 30;
let slider_container = add([
pos(x, y),
]);
let track = slider_container.add([
rect(w, slider_height),
pos(0, handle_size / 2 - slider_height / 2),
color(100, 100, 100),
area()
]);
let value = initial_val;
let handle_x = ((value - min_val) / (max_val - min_val)) * w;
let handle = slider_container.add([
rect(handle_size, handle_size),
pos(handle_x - handle_size / 2, 0),
color(255, 255, 255),
area(),
"slider_handle"
]);
let value_label = slider_container.add([
text(value.toFixed(0), { size: 16 }),
pos(w + 10, handle_size / 2 - 8),
color(255, 255, 255),
]);
let is_dragging = false;
handle.onHover(() => {
setCursor("pointer");
});
handle.onHoverEnd(() => {
if (!is_dragging) {
setCursor("default");
}
});
handle.onMousePress(() => {
is_dragging = true;
setCursor("grabbing");
});
onMouseRelease(() => {
if (is_dragging) {
is_dragging = false;
setCursor("default");
}
});
onMouseMove(() => {
if (is_dragging) {
let mouse_x = mousePos().x - slider_container.pos.x;
mouse_x = Math.max(0, Math.min(w, mouse_x));
handle.pos.x = mouse_x - handle_size / 2;
value = min_val + (mouse_x / w) * (max_val - min_val);
value_label.text = value.toFixed(0);
if (on_change) {
on_change(value);
}
}
});
track.onHover(() => {
setCursor("pointer");
});
track.onHoverEnd(() => {
setCursor("default");
});
track.onClick(() => {
if (!is_dragging) {
let mouse_x = mousePos().x - slider_container.pos.x;
mouse_x = Math.max(0, Math.min(w, mouse_x));
handle.pos.x = mouse_x - handle_size / 2;
value = min_val + (mouse_x / w) * (max_val - min_val);
value_label.text = value.toFixed(0);
if (on_change) {
on_change(value);
}
}
});
track.use(area());
return {
obj: slider_container,
getValue: () => value,
setValue: (new_val) => {
value = Math.max(min_val, Math.min(max_val, new_val));
handle_x = ((value - min_val) / (max_val - min_val)) * w;
handle.pos.x = handle_x - handle_size / 2;
value_label.text = value.toFixed(0);
}
};
}
function create_dropdown(x, y, w, h, options, initial_index, on_select) {
let selected_index = initial_index || 0;
let is_open = false;
let dropdown = add([
pos(x, y),
z(10),
]);
let selected_box = dropdown.add([
rect(w, h),
pos(0, 0),
color(60, 60, 60),
area(),
outline(2, rgb(100, 100, 100)),
]);
let selected_text = dropdown.add([
text(options[selected_index], { size: 20 }),
pos(10, h / 2 - 10),
color(255, 255, 255),
]);
let arrow = dropdown.add([
text("▼", { size: 16 }),
pos(w - 25, h / 2 - 8),
color(200, 200, 200),
]);
let options_container = null;
let option_items = [];
function create_options_menu() {
if (options_container) {
destroy(options_container);
}
options_container = dropdown.add([
pos(0, h + 2),
z(20),
]);
option_items = [];
options.forEach((option, index) => {
let option_box = options_container.add([
rect(w, h),
pos(0, index * h),
color(50, 50, 50),
area(),
outline(1, rgb(80, 80, 80)),
]);
let option_text = options_container.add([
text(option, { size: 20 }),
pos(10, index * h + h / 2 - 10),
color(255, 255, 255),
]);
option_box.onHover(() => {
option_box.color = rgb(80, 80, 80);
setCursor("pointer");
});
option_box.onHoverEnd(() => {
option_box.color = rgb(50, 50, 50);
setCursor("default");
});
option_box.onClick(() => {
selected_index = index;
selected_text.text = options[index];
close_dropdown();
if (on_select) {
on_select(options[index], index);
}
});
option_items.push({ box: option_box, text: option_text });
});
}
function open_dropdown() {
is_open = true;
arrow.text = "▲";
create_options_menu();
}
function close_dropdown() {
is_open = false;
arrow.text = "▼";
if (options_container) {
destroy(options_container);
options_container = null;
}
}
selected_box.onHover(() => {
selected_box.color = rgb(70, 70, 70);
setCursor("pointer");
});
selected_box.onHoverEnd(() => {
selected_box.color = rgb(60, 60, 60);
setCursor("default");
});
selected_box.onClick(() => {
if (is_open) {
close_dropdown();
} else {
open_dropdown();
}
});
onClick(() => {
if (is_open) {
let mouse = mousePos();
let in_bounds = mouse.x >= x && mouse.x <= x + w &&
mouse.y >= y && mouse.y <= y + h + (options.length * h);
if (!in_bounds) {
close_dropdown();
}
}
});
return {
obj: dropdown,
getValue: () => options[selected_index],
getIndex: () => selected_index,
setIndex: (index) => {
if (index >= 0 && index < options.length) {
selected_index = index;
selected_text.text = options[index];
}
},
close: close_dropdown
};
}
function create_label(x, y, label_text, font_size) {
return add([
text(label_text, {
size: font_size,
}),
color(0, 0, 0),
pos(x, y)
])
}
function horizontal_buttons(start_x, start_y, buttons, width, height, space_between) {
for (let i = 0; i < buttons.length; i++) {
create_button(start_x + i * (width + space_between), start_y, width, height, buttons[i][0], buttons[i][1], buttons[i][2], buttons[i][3])
}
}
function vertical_buttons(start_x, start_y, buttons, width, height, space_between) {
for (let i = 0; i < buttons.length; i++) {
create_button(start_x, start_y + i * (height + space_between), width, height, buttons[i][0], buttons[i][1], buttons[i][2], buttons[i][3])
}
}
function scene_lambda(scene, args) {
return () => {
go(scene, args);
}
}

View File

@@ -171,7 +171,7 @@ function setup_pumpkin(canvas_id, clearbtn_id, lightbtn_id, form_id, pattern_fie
const canvas = document.getElementById(canvas_id);
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = '/static/pumpkin.png';
img.src = '/static/graphics/pumpkin.png';
const GRID_SIZE = grid_size;
const CELL_SIZE = canvas.width / GRID_SIZE;

174
static/js/pumpkin_memory.js Normal file
View File

@@ -0,0 +1,174 @@
function game_info() {
const SETTINGS = {
"Graphics": {
"Anti-Aliasing": {"type": "bool", "default": "true"},
"Texture Filtering": {"type": "option", "options": ["Nearest", "Linear"], "default": "Linear"},
"FPS Limit": {"type": "slider", "min": 0, "max": 480, "default": 60},
},
"Sound": {
"Music": {"type": "bool", "default": "true"},
"Music Volume": {"type": "slider", "min": 0, "max": 100, "default": 50},
},
"Spooky": {
"Jumpscares": {"type": "bool", "default": "true"}
}
};
return ["Pumpkin Memory", SETTINGS];
}
function setup_game() {
loadSprite("pumpkin", "/static/graphics/pumpkin.png");
scene("game", (difficulty, pumpkin_array, revealed, found_pairs, start) => {
if (localStorage.getItem("Pumpkin Memory Jumpscares") == "true") {
if (Math.random() < 0.1) {
jumpscare();
}
}
let cols;
switch (difficulty) {
case "easy": pumpkin_pairs = 5; cols = 5; break;
case "medium": pumpkin_pairs = 10; cols = 4; break;
case "hard": pumpkin_pairs = 15; cols = 5; break;
case "extrahard": pumpkin_pairs = 20; cols = 10; break;
}
const total = pumpkin_pairs * 2;
const pumpkin_size = 100;
const space_between = 10;
const rows = Math.ceil(total / cols);
const grid_width = cols * (pumpkin_size + space_between) - space_between;
const grid_height = rows * (pumpkin_size + space_between) - space_between;
const start_x = (WIDTH - grid_width) / 2;
const start_y = (HEIGHT - grid_height) / 2;
let arr;
if (pumpkin_array == null) {
arr = [...Array(pumpkin_pairs).keys(), ...Array(pumpkin_pairs).keys()];
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
else {
arr = pumpkin_array;
}
if (revealed == null) {
revealed = [];
found_pairs = [];
start = performance.now();
}
let found_pair = null;
if (arr[revealed[0]] == arr[revealed[1]] && !found_pairs.includes(arr[revealed[0]])) {
found_pair = arr[revealed[0]];
found_pairs.push(arr[revealed[0]]);
revealed = [];
}
if (revealed.length > 2) {
revealed = [];
}
let best_time = Number(localStorage.getItem("pumpkin_memory_best_time")) || 99999;
let first_time = best_time == 99999;
const elapsed = performance.now() - start;
const best_time_display = (best_time == 99999) ? `${(elapsed / 1000).toFixed(1)}s` : `${best_time}s`;
const timer_label = create_label(400, 5, `Time spent: ${(elapsed / 1000).toFixed(1)}s Best Time: ${best_time_display}`);
const timer_interval_id = setInterval(() => {
const elapsed = performance.now() - start;
if (first_time) {
best_time = (elapsed / 1000).toFixed(1);
}
timer_label.text = `Time spent: ${(elapsed / 1000).toFixed(1)}s Best Time: ${best_time}s`
}, 100);
let jumpscare_interval_id;
if (localStorage.getItem("Pumpkin Memory Jumpscares") == "true") {
jumpscare_interval_id = setInterval(() => {
if (Math.random() < 0.05) {
jumpscare();
}
}, 1000);
}
create_button(5, 5, 150, 75, "Back", color(127, 127, 127), color(0, 0, 0, 0), () => {
if (localStorage.getItem("Pumpkin Memory Jumpscares") == "true") {
clearInterval(jumpscare_interval_id);
}
clearInterval(timer_interval_id);
go("main_menu");
})
if (pumpkin_pairs == found_pairs.length - 1) {
const elapsed = performance.now() - start;
if ((elapsed / 1000).toFixed(1) < best_time) {
best_time = (elapsed / 1000).toFixed(1);
}
localStorage.setItem(`memory_best_${difficulty}`, best_time);
const best_time_display = (best_time == 99999) ? `${(elapsed / 1000).toFixed(1)}s` : `${best_time}s`;
create_label(520, 320, `You win!\nTime took: ${(elapsed / 1000).toFixed(1)} s\nBest Time: ${best_time_display}`, 48);
return;
}
for (let i = 0; i < arr.length; i++) {
let row = Math.floor(i / cols);
let col = i % cols;
let index = i;
if (revealed.includes(i)) {
const sprite = create_sprite(start_x + col * (pumpkin_size + space_between), start_y + row * (pumpkin_size + space_between), "pumpkin");
sprite.scale = 1;
tween(sprite.scale, 0, 0.2, (val) => sprite.scale = val).then(() => {
create_label(start_x + col * (pumpkin_size + space_between) + pumpkin_size / 3, start_y + row * (pumpkin_size + space_between) + pumpkin_size / 2, arr[i], 24);
tween(sprite.scale, 1, 0.2, (val) => sprite.scale = val).then(() => {
wait(0.5, () => {
if (found_pair == null) {
clearInterval(timer_interval_id);
clearInterval(jumpscare_interval_id);
go("game", difficulty, arr, [], found_pairs, start);
}
else {
destroy(sprite);
}
});
});
})
} else if (found_pairs.includes(arr[i])) {
const sprite = create_sprite(start_x + col * (pumpkin_size + space_between), start_y + row * (pumpkin_size + space_between), "pumpkin");
sprite.opacity = 0.5;
create_label(start_x + col * (pumpkin_size + space_between) + pumpkin_size / 2, start_y + row * (pumpkin_size + space_between) + pumpkin_size / 2, arr[i], 24);
} else {
const btn = create_texturebutton(start_x + col * (pumpkin_size + space_between), start_y + row * (pumpkin_size + space_between), "pumpkin", () => {
btn.scale = 1.1;
tween(btn.scale, 1, 0.2, (val) => btn.scale = val);
clearInterval(timer_interval_id);
clearInterval(jumpscare_interval_id);
go("game", difficulty, arr, revealed.concat([index]), found_pairs, start);
})
}
}
});
scene("play", () => {
create_label(WIDTH / 2 - 16 * "Difficulty Selector".length, HEIGHT / 8, "Difficulty Selector", 56);
vertical_buttons(WIDTH / 4, HEIGHT / 4, [
["Easy", color(127, 127, 127), color(0, 0, 0, 0), scene_lambda("game", "easy")],
["Medium", color(127, 127, 127), color(0, 0, 0, 0), scene_lambda("game", "medium")],
["Hard", color(127, 127, 127), color(0, 0, 0, 0), scene_lambda("game", "hard")],
["Extra Hard", color(127, 127, 127), color(0, 0, 0, 0), scene_lambda("game", "extrahard")]
], WIDTH / 2, HEIGHT / 8, HEIGHT / 50)
});
}

184
static/js/pumpkin_roll.js Normal file
View File

@@ -0,0 +1,184 @@
function game_info() {
const SETTINGS = {
"Graphics": {
"Anti-Aliasing": {"type": "bool", "default": "true"},
"Texture Filtering": {"type": "option", "options": ["Nearest", "Linear"], "default": "Linear"},
"FPS Limit": {"type": "slider", "min": 0, "max": 480, "default": 60},
},
"Sound": {
"Music": {"type": "bool", "default": "true"},
"Music Volume": {"type": "slider", "min": 0, "max": 100, "default": 50},
},
"Input": {
"Controller Enabled": {"type": "bool", "default": "true"}
},
"Spooky": {
"Jumpscares": {"type": "bool", "default": "true"}
}
};
return ["Pumpkin Roll", SETTINGS];
}
function spawn_enemy(enemy_type) {
let enemy_width;
let enemy_height;
let start_y;
if (enemy_type == "tombstone") {
scale_f = scale(0.05);
start_y = 720;
enemy_width = 140;
enemy_height = 120;
}
else {
scale_f = scale(0.5);
start_y = 600;
enemy_width = 148;
enemy_height = 147;
}
const enemy_sprite = add([
sprite(enemy_type),
pos(1280 - enemy_width, start_y - enemy_height),
scale_f,
area(),
"enemy"
]);
enemy_sprite.onUpdate(() => {
enemy_sprite.pos.x -= 1000 * dt();
if (enemy_sprite.pos.x <= -enemy_width) {
destroy(enemy_sprite);
}
})
return enemy_sprite;
}
function setup_game() {
loadSprite("pumpkin", "/static/graphics/pumpkin.png");
loadSprite("tombstone", "/static/graphics/tombstone.png");
loadSprite("bird", "/static/graphics/bird.png");
const GRAVITY = 2500;
const JUMP_VELOCITY = -1200;
const GROUND_Y = 670;
scene("play", () => {
let score = 0;
let high_score = Number(localStorage.getItem("pumpkin_roll_highscore"));
let game_over = false;
let enemies = [];
let last_enemy_spawn = performance.now();
const score_label = create_label(480, 10, `Score: ${score} High Score: ${high_score}`);
let pumpkin_sprite = add([
sprite("pumpkin"),
pos(50, 670),
anchor("center"),
rotate(0),
area(),
"pumpkin"
])
pumpkin_sprite.isJumping = false;
let jumpscare_interval_id;
if (localStorage.getItem("Pumpkin Roll Jumpscares") == "true") {
jumpscare_interval_id = setInterval(() => {
if (Math.random() < 0.035) {
jumpscare();
}
}, 1000);
}
create_button(5, 5, 150, 75, "Back", color(127, 127, 127), color(0, 0, 0, 0), () => {
if (localStorage.getItem("Pumpkin Roll Jumpscares") == "true") {
clearInterval(jumpscare_interval_id);
}
go("main_menu");
})
onCollide("pumpkin", "enemy", () => {
if (game_over) return;
game_over = true;
for (let enemy of enemies) {
destroy(enemy);
}
if (localStorage.getItem("Pumpkin Roll Jumpscares") == "true") {
jumpscare();
}
create_label(520, 320, `Game Over!\nScore: ${Math.floor(score)}\nHigh Score: ${high_score}`, 48);
if (localStorage.getItem("Pumpkin Roll Jumpscares") == "true") {
setTimeout(jumpscare, 500);
}
})
enemy_spawn_with_check = (count) => {
const enemy_type = Math.random() < 0.75 ? "tombstone" : "bird";
for (let i = 0; i < count; i++) {
if (game_over) {
return
}
setTimeout(() => {
enemies.push(spawn_enemy(enemy_type));
}, i * 150);
}
}
enemy_spawn_with_check(1);
pumpkin_sprite.onUpdate(() => {
if (game_over) return;
score += 60 * dt();
if (Math.floor(score) > high_score) {
high_score = Math.floor(score);
localStorage.setItem("pumpkin_roll_highscore", high_score);
}
score_label.text = `Score: ${Math.floor(score)} High Score: ${high_score}`;
if ((performance.now() - last_enemy_spawn) >= 1000) {
last_enemy_spawn = performance.now();
const random = Math.random();
if (random < 0.2) {
enemy_spawn_with_check(3);
}
else if (random < 0.5) {
enemy_spawn_with_check(2);
}
else {
enemy_spawn_with_check(1);
}
}
if ((isKeyDown("space") || (localStorage.getItem("Pumpkin Roll Controller Enabled") === "true" && isGamepadButtonDown("south"))) && !pumpkin_sprite.isJumping) {
pumpkin_sprite.vy = JUMP_VELOCITY;
pumpkin_sprite.isJumping = true;
}
if (pumpkin_sprite.isJumping) {
pumpkin_sprite.angle = (pumpkin_sprite.angle + dt() * 360) % 360;
}
else {
pumpkin_sprite.angle = (pumpkin_sprite.angle + dt() * 180) % 360;
return;
}
pumpkin_sprite.vy += GRAVITY * dt();
pumpkin_sprite.pos.y += pumpkin_sprite.vy * dt();
if (pumpkin_sprite.pos.y >= GROUND_Y) {
pumpkin_sprite.pos.y = GROUND_Y;
pumpkin_sprite.isJumping = false;
pumpkin_sprite.vy = 0;
}
});
})
}

View File

@@ -0,0 +1,140 @@
function game_info() {
const SETTINGS = {
"Graphics": {
"Anti-Aliasing": {"type": "bool", "default": "true"},
"Texture Filtering": {"type": "option", "options": ["Nearest", "Linear"], "default": "Linear"},
"FPS Limit": {"type": "slider", "min": 0, "max": 480, "default": 60},
},
"Sound": {
"Music": {"type": "bool", "default": "true"},
"Music Volume": {"type": "slider", "min": 0, "max": 100, "default": 50},
},
"Spooky": {
"Jumpscares": {"type": "bool", "default": "true"}
}
};
return ["Whack a Pumpkin", SETTINGS];
}
function spawn_pumpkin(pumpkin_spaces, used_slots) {
const free_slots = pumpkin_spaces.filter((_, i) => !used_slots.has(i));
const random_index = Math.floor(Math.random() * free_slots.length);
const [x, y] = free_slots[random_index];
const pumpkin_sprite = add([
sprite("pumpkin"),
pos(x, y),
area(),
"pumpkin"
])
setInterval(() => {
destroy(pumpkin_sprite);
}, 600);
return pumpkin_sprite;
}
function setup_game() {
loadSprite("bg", "/static/graphics/whackapumpkin.png");
loadSprite("pumpkin", "/static/graphics/pumpkin.png");
const pumpkin_spaces = [
[480, 12000],
[615, 12000],
[750, 12000],
[480, 420],
[615, 420],
[750, 420],
[480, 540],
[615, 540],
[750, 540],
];
scene("play", () => {
const pumpkins = [];
const used_slots = new Set();
const start = performance.now();
let game_over = false;
let score = 0;
let high_score = Number(localStorage.getItem("whackapumpkin_high_score"));
const bg = add([
sprite("bg"),
pos(420, 15),
scale(0.85)
]);
let jumpscare_interval_id;
if (localStorage.getItem("Whack a Pumpkin Jumpscares") == "true") {
jumpscare_interval_id = setInterval(() => {
if (Math.random() < 0.05) {
jumpscare();
}
}, 1000);
}
create_button(5, 5, 150, 75, "Back", color(127, 127, 127), color(0, 0, 0, 0), () => {
game_over = true;
if (localStorage.getItem("Whack a Pumpkin Jumpscares") == "true") {
clearInterval(jumpscare_interval_id);
}
go("main_menu");
})
const info_label = create_label(525, 50, `Time left: 120s\nScore: ${score}\nHigh Score: ${high_score}`);
function spawn_pumpkins() {
pumpkins.push(spawn_pumpkin(pumpkin_spaces, used_slots));
if (!game_over) {
setTimeout(spawn_pumpkins, Math.random() * 1500);
}
}
setTimeout(spawn_pumpkins, Math.random() * 1500);
onClick("pumpkin", (pumpkin) => {
destroy(pumpkin);
if (localStorage.getItem("Whack a Pumpkin Jumpscares") == "true") {
if (Math.random() < 0.1) {
jumpscare();
}
}
score += 1;
if (score > high_score) {
high_score = score;
localStorage.setItem("whackapumpkin_high_score", high_score);
}
})
bg.onUpdate(() => {
const elapsed = performance.now() - start;
if ((elapsed / 1000) >= 120) {
create_label(520, 12020, `Game Over!\nScore: ${score}\nHigh Score: ${high_score}`, 48);
game_over = true;
for (const pumpkin of pumpkins) {
destroy(pumpkin);
}
destroy(bg);
destroy(info_label);
if (localStorage.getItem("Whack a Pumpkin Jumpscares") == "true") {
setTimeout(jumpscare, 500);
}
}
else {
info_label.text = `Time left: ${(120 - (elapsed / 1000)).toFixed(1)}s\nScore: ${score}\nHigh Score: ${high_score}`;
}
})
});
}

BIN
static/sound/jumpscare.mp3 Normal file

Binary file not shown.

BIN
static/sound/music.mp3 Normal file

Binary file not shown.

View File

@@ -5,8 +5,10 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css2?family=New Rocker&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
<script src="/static/pumpkin.js"></script>
<script src="https://unpkg.com/kaplay@3001.0.12/dist/kaplay.js"></script>
<script src="/static/js/pumpkin.js"></script>
<title>{% block title %} {% endblock %}</title>
<style>
.spider-web-1 {
@@ -30,20 +32,19 @@
z-index: 1;
}
* {
font-family: 'Creepster', cursive !important;
font-family: 'New Rocker', cursive !important;
}
body {
background: linear-gradient(135deg, #0d0618 0%, #1a0b2e 50%, #2d1b4e 100%);
}
</style>
<link href="https://fonts.googleapis.com/css2?family=Creepster&display=swap" rel="stylesheet">
{% block head %} {% endblock %}
</head>
<body>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="/" style="font-size: 2rem; font-family: 'Creepster', cursive; color: orange;">🎃 LoginWeen</a>
<a class="navbar-brand" href="/" style="font-size: 2rem; font-family: 'New Rocker', cursive; color: orange;">🎃 LoginWeen</a>
<button class="navbar-toggler" type="button", data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>

View File

@@ -7,6 +7,15 @@
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/pumpkin_roll">Pumpkin Roll</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/pumpkin_memory">Pumpkin Memory</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/whack_a_pumpkin">Whack a Pumpkin</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/countdown">Countdown</a>
</li>
@@ -26,9 +35,6 @@
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/countdown">Countdown</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout">Logout</a>
</li>
{% endif %}
{% endblock %}
@@ -54,7 +60,7 @@ function update() {
let year = current_time.getFullYear();
const halloween_time = new Date(year, 9, 31);
const after_halloween = new Date(year, 15, 1);
const after_halloween = new Date(year, 10, 1);
if (current_time > halloween_time) {
if (current_time < after_halloween) {
@@ -62,7 +68,7 @@ function update() {
return;
}
else {
halloween.setFullYear(year + 1);
halloween_time.setFullYear(year + 1);
}
}

33
templates/gamebase.jinja2 Normal file
View File

@@ -0,0 +1,33 @@
{% extends "base.jinja2" %}
{% block nav %}
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
{% block gamenav %}{% endblock %}
<li class="nav-item">
<a class="nav-link" href="/countdown">Countdown</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/profile">Profile</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout">Logout</a>
</li>
{% endblock %}
{% block body %}
<div class="position-absolute top-50 start-50 translate-middle text-center">
<div id="game-container">
<canvas width="1280", height="720" id="canvas"></canvas>
</div>
</div>
<script src="/static/js/gameui.js"></script>
<script src="/static/js/game.js"></script>
{% block game_js %}{% endblock %}
<script>
window.addEventListener("DOMContentLoaded", () => {
start_game();
});
</script>
{% endblock %}

View File

@@ -6,6 +6,15 @@
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/pumpkin_roll">Pumpkin Roll</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/pumpkin_memory">Pumpkin Memory</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/whack_a_pumpkin">Whack a Pumpkin</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/countdown">Countdown</a>
</li>

View File

@@ -12,9 +12,6 @@
<li class="nav-item">
<a class="nav-link" href="/countdown">Countdown</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout">Logout</a>
</li>
{% endblock %}
{% block body %}

View File

@@ -6,6 +6,15 @@
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/pumpkin_roll">Pumpkin Roll</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/pumpkin_memory">Pumpkin Memory</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/whack_a_pumpkin">Whack a Pumpkin</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/countdown">Countdown</a>
</li>
@@ -26,6 +35,12 @@
{% if logged_in_account %}
<p class="mb-1">Logged in as: {{ username }}</p>
<p class="mb-1" id="pumpkin_roll_highscore">Pumpkin Roll High Score: Loading...</p>
<p class="mb-1" id="whackapumpkin_highscore">Whack a Pumpkin High Score: Loading...</p>
<p class="mb-1" id="memory_best_easy">Pumpkin Memory (Easy) Best Time: Loading...</p>
<p class="mb-1" id="memory_best_medium">Pumpkin Memory (Medium) Best Time: Loading...</p>
<p class="mb-1" id="memory_best_hard">Pumpkin Memory (Hard) Best Time: Loading...</p>
<p class="mb-1" id="memory_best_extrahard">Pumpkin Memory (Extra hard) Best Time: Loading...</p>
{% endif %}
</div>
</div>
@@ -87,7 +102,25 @@
</div>
<script>
setup_pumpkin("current_pumpkin_canvas", "currentclearBtn", "currentlightBtn", "change_pattern_form", "current_pattern_field", {{ grid_size }})
setup_pumpkin("new_pumpkin_canvas", "newclearBtn", "newlightBtn", "change_pattern_form", "new_pattern_field", {{ grid_size }})
setup_pumpkin("current_pumpkin_canvas", "currentclearBtn", "currentlightBtn", "change_pattern_form", "current_pattern_field", {{ grid_size }});
setup_pumpkin("new_pumpkin_canvas", "newclearBtn", "newlightBtn", "change_pattern_form", "new_pattern_field", {{ grid_size }});
function formatTime(time) {
return time !== "None" ? `${time}s` : time;
}
pumpkin_roll_highscore = localStorage.getItem("pumpkin_roll_highscore") || "None"
whackapumpkin_highscore = localStorage.getItem("whackapumpkin_highscore") || "None"
memory_best_easy = localStorage.getItem("memory_best_easy") || "None"
memory_best_medium = localStorage.getItem("memory_best_medium") || "None"
memory_best_hard = localStorage.getItem("memory_best_hard") || "None"
memory_best_extrahard = localStorage.getItem("memory_best_extrahard") || "None"
document.getElementById("pumpkin_roll_highscore").textContent = `Pumpkin Roll High Score: ${pumpkin_roll_highscore}`;
document.getElementById("whackapumpkin_highscore").textContent = `Whack a Pumpkin High Score: ${whackapumpkin_highscore}`;
document.getElementById("memory_best_easy").textContent = `Pumpkin Memory (Easy) Best Time: ${formatTime(memory_best_easy)}`;
document.getElementById("memory_best_medium").textContent = `Pumpkin Memory (Medium) Best Time: ${formatTime(memory_best_medium)}`;
document.getElementById("memory_best_hard").textContent = `Pumpkin Memory (Hard) Best Time: ${formatTime(memory_best_hard)}`;
document.getElementById("memory_best_extrahard").textContent = `Pumpkin Memory (Extra Hard) Best Time: ${formatTime(memory_best_extrahard)}`;
</script>
{% endblock body %}

View File

@@ -0,0 +1,17 @@
{% extends "gamebase.jinja2" %}
{% block gamenav %}
<li class="nav-item">
<a class="nav-link" href="/pumpkin_roll">Pumpkin Roll</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/pumpkin_memory">Pumpkin Memory</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/whack_a_pumpkin">Whack a Pumpkin</a>
</li>
{% endblock %}
{% block title %}Pumpkin Memory{% endblock title %}
{% block game_js %}<script src="/static/js/pumpkin_memory.js"></script>{% endblock %}

View File

@@ -0,0 +1,17 @@
{% extends "gamebase.jinja2" %}
{% block gamenav %}
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/pumpkin_roll">Pumpkin Roll</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/pumpkin_memory">Pumpkin Memory</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/whack_a_pumpkin">Whack a Pumpkin</a>
</li>
{% endblock %}
{% block title %}Pumpkin Roll{% endblock title %}
{% block game_js %}<script src="/static/js/pumpkin_roll.js"></script>{% endblock %}

View File

@@ -12,9 +12,6 @@
<li class="nav-item">
<a class="nav-link" href="/countdown">Countdown</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout">Logout</a>
</li>
{% endblock %}
{% block body %}

View File

@@ -0,0 +1,17 @@
{% extends "gamebase.jinja2" %}
{% block gamenav %}
<li class="nav-item">
<a class="nav-link" href="/pumpkin_roll">Pumpkin Roll</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/pumpkin_memory">Pumpkin Memory</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/whack_a_pumpkin">Whack a Pumpkin</a>
</li>
{% endblock %}
{% block title %}Whack a Pumpkin{% endblock title %}
{% block game_js %}<script src="/static/js/whack_a_pumpkin.js"></script>{% endblock %}