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;
}
#debug-query-log {
.debug-query-log {
padding: 2rem;
font-size: 14px;
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
*/
router_get($r, '/world', function () {
auth_only_and_must_have_character();
$GLOBALS['active_nav_tab'] = 'world';
echo page('world/base');
});
router_get($r, '/world', 'world_controller_get');
router_post($r, '/move', 'world_controller_move_post');
/*
Router

View File

@ -6,22 +6,23 @@ session_start();
// Source libraries
require_once SRC . '/helpers.php';
require_once SRC . '/env.php';
require_once SRC . '/database.php';
require_once SRC . '/auth.php';
require_once SRC . '/router.php';
require_once SRC . '/components.php';
require_once SRC . '/render.php';
require_once SRC . '/util/env.php';
require_once SRC . '/util/database.php';
require_once SRC . '/util/auth.php';
require_once SRC . '/util/router.php';
require_once SRC . '/util/components.php';
require_once SRC . '/util/render.php';
// Database models
require_once SRC . '/models/user.php';
require_once SRC . '/models/session.php';
require_once SRC . '/models/token.php';
require_once SRC . '/models/char.php';
require_once SRC . '/model/user.php';
require_once SRC . '/model/session.php';
require_once SRC . '/model/token.php';
require_once SRC . '/model/char.php';
// Controllers
require_once SRC . '/controllers/char.php';
require_once SRC . '/controllers/auth.php';
require_once SRC . '/controller/char.php';
require_once SRC . '/controller/auth.php';
require_once SRC . '/controller/world.php';
// Track the start time of the request
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;
}
/**
* 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.
*/
@ -231,3 +250,11 @@ function ce($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()
{
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()
{
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()
{
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()
{
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)
{
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>
<p class="mb-2"><?= $GLOBALS['queries'] ?> queries were executed.</p>
<?php

View File

@ -1,4 +1,4 @@
<div id="debug-query-log">
<div class="debug-query-log">
<h3>Stopwatches</h3>
<p class="mb-2">Page execution took <?= number_format((microtime(true) - START_TIME), 10) ?> 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">
<title>Dragon Knight</title>
<link rel="stylesheet" href="/assets/css/dragon.css">
<script src="/assets/scripts/htmx.js"></script>
</head>
<body>
<header>

View File

@ -1,3 +1,113 @@
<div id="target_world_map">
<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>