Compare commits
2 Commits
a564a96625
...
a89d530cd0
Author | SHA1 | Date | |
---|---|---|---|
a89d530cd0 | |||
b9a54859ce |
BIN
database/live.db
BIN
database/live.db
Binary file not shown.
|
@ -324,6 +324,10 @@ span.badge {
|
||||||
background-color: #444c55;
|
background-color: #444c55;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.green {
|
||||||
|
background-color: #a6e3a1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-1 { margin-bottom: 0.25rem; margin-top: 0.25rem; }
|
.my-1 { margin-bottom: 0.25rem; margin-top: 0.25rem; }
|
||||||
|
@ -414,7 +418,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;
|
||||||
|
@ -517,3 +521,14 @@ body::-webkit-scrollbar-thumb {
|
||||||
border-color: #3D444C #2F353B #2C3137;
|
border-color: #3D444C #2F353B #2C3137;
|
||||||
box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset;
|
box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#canvas-container {
|
||||||
|
& > canvas {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 440px;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -86,8 +86,6 @@
|
||||||
|
|
||||||
& > span.selected {
|
& > span.selected {
|
||||||
display: none;
|
display: none;
|
||||||
margin-left: 1rem;
|
|
||||||
color: #a6e3a1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
public/assets/scripts/htmx.js
Normal file
1
public/assets/scripts/htmx.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
|
60
src/controller/world.php
Normal file
60
src/controller/world.php
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<?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()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
This endpoint is used to move the character around the world. The client sends a POST request with the direction
|
||||||
|
they want to move the character. The server will update the character's position in the database and return the
|
||||||
|
new position to the client.
|
||||||
|
|
||||||
|
We should only be using this endpoint as an AJAX request from the world page. Since we don't need all the character's
|
||||||
|
data to move them, we can just get and update their lcoation using the user's currently selected character ID.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ajax_only(); auth_only(); csrf_ensure();
|
||||||
|
|
||||||
|
define('directions', [
|
||||||
|
[0, -1], // Up
|
||||||
|
[0, 1], // Down
|
||||||
|
[-1, 0], // Left
|
||||||
|
[1, 0] // Right
|
||||||
|
]);
|
||||||
|
|
||||||
|
// direction must exist
|
||||||
|
$d = (int) $_POST['direction'] ?? -1;
|
||||||
|
|
||||||
|
// Update the character's position
|
||||||
|
// 0 = up, 1 = down, 2 = left, 3 = right
|
||||||
|
$x = location('x');
|
||||||
|
$y = location('y');
|
||||||
|
|
||||||
|
if (isset(directions[$d])) {
|
||||||
|
$x += directions[$d][0];
|
||||||
|
$y += directions[$d][1];
|
||||||
|
} else {
|
||||||
|
router_error(999);
|
||||||
|
}
|
||||||
|
|
||||||
|
$r = db_query(db_live(), 'UPDATE char_locations SET x = :x, y = :y WHERE char_id = :c', [
|
||||||
|
':x' => $x,
|
||||||
|
':y' => $y,
|
||||||
|
':c' => user('char_id')
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($r === false) throw new Exception('Failed to move character. (wcmp)');
|
||||||
|
|
||||||
|
json_response(['x' => $x, 'y' => $y]);
|
||||||
|
}
|
|
@ -59,12 +59,7 @@ function csrf()
|
||||||
*/
|
*/
|
||||||
function csrf_verify($token)
|
function csrf_verify($token)
|
||||||
{
|
{
|
||||||
if (hash_equals($_SESSION['csrf'] ?? '', $token)) {
|
return hash_equals($_SESSION['csrf'] ?? '', $token);
|
||||||
$_SESSION['csrf'] = token();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -185,6 +180,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' => user('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 +245,37 @@ function ce($condition, $value, $or = '')
|
||||||
{
|
{
|
||||||
echo $condition ? $value : $or;
|
echo $condition ? $value : $or;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether the request is an HTMX request.
|
||||||
|
*/
|
||||||
|
function is_htmx()
|
||||||
|
{
|
||||||
|
return isset($_SERVER['HTTP_HX_REQUEST']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether the request is an AJAX (fetch) request.
|
||||||
|
*/
|
||||||
|
function is_ajax()
|
||||||
|
{
|
||||||
|
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limit a request to AJAX only.
|
||||||
|
*/
|
||||||
|
function ajax_only()
|
||||||
|
{
|
||||||
|
if (!is_ajax()) router_error(418);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a JSON response with the given data.
|
||||||
|
*/
|
||||||
|
function json_response($data)
|
||||||
|
{
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode($data);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
define('DBP', SRC . '/../database');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a connection to a database.
|
* Open a connection to a database.
|
||||||
*/
|
*/
|
||||||
|
@ -22,7 +24,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(DBP . '/auth.db');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,7 +32,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(DBP . '/live.db');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,7 +41,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(DBP . '/fights.db');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,7 +50,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(DBP . '/blueprints.db');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
function template($name)
|
function template($name)
|
||||||
{
|
{
|
||||||
return __DIR__ . "/../templates/$name.php";
|
return SRC . "/../templates/$name.php";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -3,6 +3,6 @@
|
||||||
<label for="char_<?= $id ?>">
|
<label for="char_<?= $id ?>">
|
||||||
<?= $char['name'] ?>
|
<?= $char['name'] ?>
|
||||||
<span class="badge"><?= $char['level'] ?></span>
|
<span class="badge"><?= $char['level'] ?></span>
|
||||||
<span class="selected">Active</span>
|
<span class="badge green selected">Active</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,3 +1,163 @@
|
||||||
<h1>World</h1>
|
<h1>World</h1>
|
||||||
|
<p>Use WASD keys to move the character</p>
|
||||||
|
<p>Current location: <span id="char_x"><?= location('x') ?></span>, <span id="char_y"><?= location('y') ?></span></p>
|
||||||
|
|
||||||
<?= render('components/world_map') ?>
|
<div id="canvas-container">
|
||||||
|
<canvas id="canvas"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const game = {
|
||||||
|
canvas: document.getElementById('canvas'),
|
||||||
|
csrf: '<?= csrf() ?>',
|
||||||
|
tiles: {
|
||||||
|
size: 32,
|
||||||
|
colors: {
|
||||||
|
0: '#fff',
|
||||||
|
1: '#f00'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const map = [
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
]
|
||||||
|
|
||||||
|
let loc_span = {
|
||||||
|
x: document.getElementById('char_x'),
|
||||||
|
y: document.getElementById('char_y')
|
||||||
|
}
|
||||||
|
|
||||||
|
let player = { x: <?= location('x') ?>, y: <?= location('y') ?> }
|
||||||
|
let camera = { x: 0, y: 0 }
|
||||||
|
let visible = { x: 0, y: 0 }
|
||||||
|
|
||||||
|
function updateCanvasSize() {
|
||||||
|
game.canvas.width = game.canvas.clientWidth
|
||||||
|
game.canvas.height = game.canvas.clientHeight
|
||||||
|
visible.x = Math.ceil(game.canvas.width / game.tiles.size)
|
||||||
|
visible.y = Math.ceil(game.canvas.height / game.tiles.size)
|
||||||
|
updateCamera()
|
||||||
|
render()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupEventListeners() {
|
||||||
|
window.addEventListener('resize', updateCanvasSize)
|
||||||
|
window.addEventListener('keydown', handleKeyPress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle keyboard input
|
||||||
|
function handleKeyPress(e) {
|
||||||
|
let moved = false;
|
||||||
|
const newPos = { ...player }
|
||||||
|
|
||||||
|
// 0 = up, 1 = down, 2 = left, 3 = right
|
||||||
|
direction = {
|
||||||
|
'w': 0,
|
||||||
|
's': 1,
|
||||||
|
'a': 2,
|
||||||
|
'd': 3,
|
||||||
|
'ArrowUp': 0,
|
||||||
|
'ArrowDown': 1,
|
||||||
|
'ArrowLeft': 2,
|
||||||
|
'ArrowRight': 3
|
||||||
|
}[e.key];
|
||||||
|
|
||||||
|
if (direction !== undefined) {
|
||||||
|
// Execute a POST request to /move. If successful, the server will return a new x,y position
|
||||||
|
fetch('/move', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
},
|
||||||
|
body: `direction=${direction}&csrf=${game.csrf}`
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
response.json().then(data => {
|
||||||
|
map[player.y][player.x] = 0
|
||||||
|
map[data.y][data.x] = 1
|
||||||
|
player = data
|
||||||
|
loc_span.x.textContent = player.x
|
||||||
|
loc_span.y.textContent = player.y
|
||||||
|
updateCamera()
|
||||||
|
render()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error('Failed to move character');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update camera position
|
||||||
|
function updateCamera() {
|
||||||
|
camera.x = player.x * game.tiles.size - canvas.width / 2;
|
||||||
|
camera.y = player.y * game.tiles.size - canvas.height / 2;
|
||||||
|
|
||||||
|
// Clamp camera to map bounds
|
||||||
|
camera.x = Math.max(0, Math.min(camera.x,
|
||||||
|
map[0].length * game.tiles.size - canvas.width));
|
||||||
|
camera.y = Math.max(0, Math.min(camera.y,
|
||||||
|
map.length * game.tiles.size - canvas.height));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the game
|
||||||
|
function render() {
|
||||||
|
const ctx = game.canvas.getContext('2d')
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, game.canvas.width, game.canvas.height)
|
||||||
|
|
||||||
|
// Calculate visible tile range
|
||||||
|
const startTileX = Math.floor(camera.x / game.tiles.size)
|
||||||
|
const startTileY = Math.floor(camera.y / game.tiles.size)
|
||||||
|
const endTileX = startTileX + visible.x + 1
|
||||||
|
const endTileY = startTileY + visible.y + 1
|
||||||
|
|
||||||
|
// Only render visible tiles
|
||||||
|
for (let y = startTileY; y < endTileY; y++) {
|
||||||
|
if (y >= map.length) continue
|
||||||
|
|
||||||
|
for (let x = startTileX; x < endTileX; x++) {
|
||||||
|
if (x >= map[0].length) continue
|
||||||
|
|
||||||
|
const screenX = x * game.tiles.size - camera.x
|
||||||
|
const screenY = y * game.tiles.size - camera.y
|
||||||
|
|
||||||
|
if (map[y][x] === 1) {
|
||||||
|
// Draw player
|
||||||
|
ctx.fillStyle = 'red';
|
||||||
|
ctx.fillRect(screenX, screenY, game.tiles.size, game.tiles.size)
|
||||||
|
} else if (map[y][x] === 2) {
|
||||||
|
// Draw wall
|
||||||
|
ctx.fillStyle = 'black';
|
||||||
|
ctx.fillRect(screenX, screenY, game.tiles.size, game.tiles.size)
|
||||||
|
} else {
|
||||||
|
// Draw empty tile
|
||||||
|
ctx.strokeStyle = 'gray';
|
||||||
|
ctx.fillStyle = 'white';
|
||||||
|
ctx.fillRect(screenX, screenY, game.tiles.size, game.tiles.size)
|
||||||
|
ctx.strokeRect(screenX, screenY, game.tiles.size, game.tiles.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
updateCanvasSize()
|
||||||
|
setupEventListeners()
|
||||||
|
render()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user