src restructure, world map

This commit is contained in:
Sky Johnson 2024-10-10 19:14:14 -05:00
parent a564a96625
commit b9a54859ce
28 changed files with 219 additions and 60 deletions

Binary file not shown.

BIN
database/live.db-shm Normal file

Binary file not shown.

BIN
database/live.db-wal Normal file

Binary file not shown.

View File

@ -414,7 +414,7 @@ span.badge {
left: 0; left: 0;
} }
#debug-query-log { .debug-query-log {
padding: 2rem; padding: 2rem;
font-size: 14px; font-size: 14px;
color: #666; color: #666;

File diff suppressed because one or more lines are too long

View File

@ -38,11 +38,8 @@ router_post($r, '/character/delete', 'char_controller_delete_post');
/* /*
World World
*/ */
router_get($r, '/world', function () { router_get($r, '/world', 'world_controller_get');
auth_only_and_must_have_character(); router_post($r, '/move', 'world_controller_move_post');
$GLOBALS['active_nav_tab'] = 'world';
echo page('world/base');
});
/* /*
Router Router

View File

@ -6,22 +6,23 @@ session_start();
// Source libraries // Source libraries
require_once SRC . '/helpers.php'; require_once SRC . '/helpers.php';
require_once SRC . '/env.php'; require_once SRC . '/util/env.php';
require_once SRC . '/database.php'; require_once SRC . '/util/database.php';
require_once SRC . '/auth.php'; require_once SRC . '/util/auth.php';
require_once SRC . '/router.php'; require_once SRC . '/util/router.php';
require_once SRC . '/components.php'; require_once SRC . '/util/components.php';
require_once SRC . '/render.php'; require_once SRC . '/util/render.php';
// Database models // Database models
require_once SRC . '/models/user.php'; require_once SRC . '/model/user.php';
require_once SRC . '/models/session.php'; require_once SRC . '/model/session.php';
require_once SRC . '/models/token.php'; require_once SRC . '/model/token.php';
require_once SRC . '/models/char.php'; require_once SRC . '/model/char.php';
// Controllers // Controllers
require_once SRC . '/controllers/char.php'; require_once SRC . '/controller/char.php';
require_once SRC . '/controllers/auth.php'; require_once SRC . '/controller/auth.php';
require_once SRC . '/controller/world.php';
// Track the start time of the request // Track the start time of the request
define('START_TIME', microtime(true)); define('START_TIME', microtime(true));

56
src/controller/world.php Normal file
View File

@ -0,0 +1,56 @@
<?php
/**
* Print the world page.
*/
function world_controller_get()
{
auth_only_and_must_have_character();
$GLOBALS['active_nav_tab'] = 'world';
echo page('world/base');
}
/**
* Handle a request to move a character.
*/
function world_controller_move_post()
{
auth_only_and_must_have_character(); csrf_ensure();
// direction must exist
$direction = $_POST['direction'] ?? false;
// direction must be valid; 0-3 are sent from the client
if (!is_numeric($direction) || $direction < 0 || $direction > 3 || $direction === false) router_error(999);
// Update the character's position
// 0 = up, 1 = down, 2 = left, 3 = right
$x = location('x');
$y = location('y');
switch ($direction) {
case 0: $y--; break;
case 1: $y++; break;
case 2: $x--; break;
case 3: $x++; break;
}
// Update the character's position
$r = db_query(db_live(), 'UPDATE char_locations SET x = :x, y = :y WHERE char_id = :c', [
':x' => $x,
':y' => $y,
':c' => char('id')
]);
// If the query failed, throw an error
if ($r === false) throw new Exception('Failed to move character. (wcmp)');
// If this is an HTMX request, return the new world page
if (is_htmx()) {
echo render('pages/world/base');
exit;
}
// Redirect back to the world page
redirect('/world');
}

View File

@ -185,6 +185,25 @@ function wallet($field = '')
return $GLOBALS['wallet'][$field] ?? false; return $GLOBALS['wallet'][$field] ?? false;
} }
/**
* Access the character location. On first execution it will populate $GLOBALS['location'] with the location data. This
* way the data is up to date with every request without having to query the database every use within, for example, a
* template. Will return false if the field does not exist, or the entire location array if no field is specified.
*/
function location($field = '')
{
if (empty($GLOBALS['location'])) {
$GLOBALS['location'] = db_query(
db_live(),
"SELECT * FROM char_locations WHERE char_id = :c",
[':c' => char('id')]
)->fetchArray(SQLITE3_ASSOC);
}
if ($field === '') return $GLOBALS['location'];
return $GLOBALS['location'][$field] ?? false;
}
/** /**
* Format an array of strings to a ul element. * Format an array of strings to a ul element.
*/ */
@ -231,3 +250,11 @@ function ce($condition, $value, $or = '')
{ {
echo $condition ? $value : $or; echo $condition ? $value : $or;
} }
/**
* Get whether the request is an HTMX request.
*/
function is_htmx(): bool
{
return isset($_SERVER['HTTP_HX_REQUEST']);
}

View File

@ -22,7 +22,7 @@ function db_open($path)
*/ */
function db_auth() function db_auth()
{ {
return $GLOBALS['db_auth'] ??= db_open(__DIR__ . '/../database/auth.db'); return $GLOBALS['db_auth'] ??= db_open(SRC . '/../database/auth.db');
} }
/** /**
@ -30,7 +30,7 @@ function db_auth()
*/ */
function db_live() function db_live()
{ {
return $GLOBALS['db_live'] ??= db_open(__DIR__ . '/../database/live.db'); return $GLOBALS['db_live'] ??= db_open(SRC . '/../database/live.db');
} }
@ -39,7 +39,7 @@ function db_live()
*/ */
function db_fights() function db_fights()
{ {
return $GLOBALS['db_fights'] ??= db_open(__DIR__ . '/../database/fights.db'); return $GLOBALS['db_fights'] ??= db_open(SRC . '/../database/fights.db');
} }
@ -48,7 +48,7 @@ function db_fights()
*/ */
function db_blueprints() function db_blueprints()
{ {
return $GLOBALS['db_blueprints'] ??= db_open(__DIR__ . '/../database/blueprints.db'); return $GLOBALS['db_blueprints'] ??= db_open(SRC . '/../database/blueprints.db');
} }
/** /**

View File

@ -5,7 +5,7 @@
*/ */
function template($name) function template($name)
{ {
return __DIR__ . "/../templates/$name.php"; return SRC . "/../templates/$name.php";
} }
/** /**

View File

@ -1,4 +1,4 @@
<div id="debug-query-log"> <div class="debug-query-log">
<h3>Query Log</h3> <h3>Query Log</h3>
<p class="mb-2"><?= $GLOBALS['queries'] ?> queries were executed.</p> <p class="mb-2"><?= $GLOBALS['queries'] ?> queries were executed.</p>
<?php <?php

View File

@ -1,4 +1,4 @@
<div id="debug-query-log"> <div class="debug-query-log">
<h3>Stopwatches</h3> <h3>Stopwatches</h3>
<p class="mb-2">Page execution took <?= number_format((microtime(true) - START_TIME), 10) ?> seconds.</p> <p class="mb-2">Page execution took <?= number_format((microtime(true) - START_TIME), 10) ?> seconds.</p>
<p>Bootstrap: <?= stopwatch_get('bootstrap') ?> seconds</p> <p>Bootstrap: <?= stopwatch_get('bootstrap') ?> seconds</p>

View File

@ -1,34 +0,0 @@
<canvas></canvas>
<script>
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 600;
const tile_height = 32;
const tile_width = 32;
const map = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
// render the map
map.forEach((tile, index) => {
const x = (index % 10) * tile_width;
const y = Math.floor(index / 10) * tile_height;
ctx.fillStyle = tile === 0 ? 'black' : 'white';
ctx.fillRect(x, y, tile_width, tile_height);
});
</script>

View File

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dragon Knight</title> <title>Dragon Knight</title>
<link rel="stylesheet" href="/assets/css/dragon.css"> <link rel="stylesheet" href="/assets/css/dragon.css">
<script src="/assets/scripts/htmx.js"></script>
</head> </head>
<body> <body>
<header> <header>

View File

@ -1,3 +1,113 @@
<div id="target_world_map">
<h1>World</h1> <h1>World</h1>
<p>Use WASD keys to move the character</p>
<p>Current location: <?= location('x') ?>, <?= location('y') ?></p>
<?= render('components/world_map') ?> <canvas id="canvas"></canvas>
<form hx-post="/move" hx-target="target_world_map" hx-swap="outerHTML" id="form_move">
<?= csrf_field() ?>
<input type="hidden" name="direction" value="">
</form>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let char_x = <?= location('x') ?>;
let char_y = <?= location('y') ?>;
// Configuration
const TILE_SIZE = 40; // Fixed size for each tile
// Sample tile map (0 = empty, 1 = filled)
const tileMap = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
// Turn the value at the player's coordinates to be 1 in the map array
tileMap[char_y][char_x] = 1;
const tileColors = {
0: '#fff', // Empty tile
1: '#333' // Wall tile
};
function resizeCanvas() {
// Calculate the actual dimensions needed for the map
const mapWidth = tileMap[0].length * TILE_SIZE;
const mapHeight = tileMap.length * TILE_SIZE;
// Get the container's width
const containerWidth = canvas.parentElement.clientWidth;
// Calculate the scale factor to fit the map width to the container
const scale = Math.min(1, containerWidth / mapWidth);
// Set canvas size using the scale factor
canvas.width = mapWidth * scale;
canvas.height = mapHeight * scale;
// Scale the context to maintain tile dimensions
ctx.scale(scale, scale);
// Render the map after resize
renderMap();
}
function renderMap() {
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw each tile
for (let row = 0; row < tileMap.length; row++) {
for (let col = 0; col < tileMap[row].length; col++) {
const tileType = tileMap[row][col];
const x = col * TILE_SIZE;
const y = row * TILE_SIZE;
ctx.fillStyle = tileColors[tileType];
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE);
// Draw tile borders
ctx.strokeStyle = '#999';
ctx.strokeRect(x, y, TILE_SIZE, TILE_SIZE);
}
}
}
// Initial setup
resizeCanvas();
// Handle window resizing
window.addEventListener('resize', resizeCanvas);
// On WASD key press, send a POST request to move the character using the form
window.addEventListener('keydown', (e) => {
if (!['w', 'a', 's', 'd'].includes(e.key)) return;
el = document.querySelector('input[name="direction"]');
// update the direction input to be 0-3 based on the key pressed
// 0 = up, 1 = down, 2 = left, 3 = right
el.value = {
'w': 0,
's': 1,
'a': 2,
'd': 3
}[e.key];
htmx.trigger(document.getElementById('form_move'), 'submit');
});
</script>
</div>