Code Walkthrough¶
main.js¶
window.WIDTH = 1260;
window.HEIGHT = 720;
window.GS = 32; // GRID SIZE
window.RATIO = 1.75
window.ROWS = 23
window.COLUMNS = 40
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.strokeStyle = 'black';
var activeKey = null;
var interval;
function setLandscape() {
if (interval) {
clearInterval(interval)
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (screen.width> screen.height) {
window.landscape = true;
document.getElementById("portrait").style.display = "none";
document.getElementById("portrait").style.color = "white";
document.body.style.backgroundColor = "black";
draw()
} else {
window.landscape = false;
document.getElementById("portrait").style.display = "block";
document.getElementById("portrait").style.color = "black";
document.body.style.backgroundColor = "white";
}
}
setLandscape();
window.onresize = setLandscape;
window.FRAME = 0;
window.DIRECTIONS = {
"DOWN" : 0,
"LEFT" : 1,
"RIGHT" : 2,
"UP" : 3,
"MOUSE": 4
}
var playerMoving = -1;
var speed;
var playerPosition;
var nearestRegion = null;
var moveTo = null;
var regions = {
"wall": {
"regions" : [
[[0,0], [0,5], [57, 5], [57, 0]],
[[55, 5], [55, 17], [58, 17], [58, 5]],
[[64, 14], [64,16], [79, 16], [79, 14]]
],
},
"code library": {
"regions": [
[[18, 3], [18, 6], [21,6], [21, 3]]
]
},
"jukebox": {
"regions": [
[[34, 4], [34, 7], [37,7], [37, 4]]
]
},
"demos": {
"regions": [
[[25, 30], [25, 34], [30, 34], [30, 30] ]
]
},
"xyzzy": {
"regions": [
[[63, 6], [63, 8], [66, 8], [66, 6]]
]
}
}
const pointInRect = ({x1, y1, x2, y2}, {x, y}) => {
return ((x > x1 && x < x2) && (y > y1 && y < y2))
}
function in_region(position, vicinity) {
var matched_regions = [];
Object.keys(regions).map(function (region_name) {
var region_data = regions[region_name]
var region_rects = region_data["regions"]
region_rects.map(function (r) {
var x1 = r[0][0] / 2.0 - vicinity
var y1 = r[0][1] / 2.0 - vicinity
var x2 = r[2][0] / 2.0 + vicinity
var y2 = r[2][1] / 2.0 + vicinity
if (pointInRect({x1, y1, x2, y2}, {x: position[0], y: position[1]})) {
matched_regions.push([region_name, r])
}
})
})
return matched_regions;
}
function div(a, b) {
return Math.round(a / b - 0.5);
}
function DisplayPropertyNames(obj) {
var names = "";
for (var name in obj) names += name + " / ";
alert(names);
}
function mainLoop() {
FRAME++;
ctx.clearRect(0, 0, WIDTH * SCALE_FACTOR, HEIGHT * SCALE_FACTOR);
draw_image([0,0])
var direction = playerMoving;
if (playerMoving !== -1) {
var px = playerPosition[0]
var py = playerPosition[1]
if (playerMoving == DIRECTIONS["UP"]) {
var vx = 0;
var vy = - speed;
} else if (playerMoving == DIRECTIONS["RIGHT"]) {
var vx = speed;
var vy = 0;
} else if (playerMoving == DIRECTIONS["DOWN"]) {
var vx = 0;
var vy = speed;
} else if (playerMoving == DIRECTIONS["LEFT"]) {
var vx = - speed;
var vy = 0;
}
if (playerMoving == DIRECTIONS["MOUSE"]) {
var diff = [(playerPosition[0] - moveTo[0]), (playerPosition[1] - moveTo[1])]
if (diff[0] < 0) {
var vx = speed;
} else {
var vx = - speed;
}
if (diff[1] < 0) {
var vy = speed;
direction = DIRECTIONS["DOWN"];
} else {
var vy = - speed;
direction = DIRECTIONS["UP"];
}
if ((Math.abs(diff[0]) <= 1) && (Math.abs(diff[1]) <= 1)) {
playerMoving = -1;
moveTo = null;
}
}
px += vx;
py += vy;
let ret = in_region([px, py], 0)
if (ret.length === 0) {
playerPosition[0] = px;
playerPosition[1] = py;
}
}
draw_character([0,0], direction, playerPosition);
draw_xyzzy();
let ret = in_region(playerPosition, 1.5);
if (ret.length !== 0) {
ret.map(function (val) {
if (val[0] !== "wall") {
nearestRegion = val[0];
var x1 = val[1][0][0] / 2.0;
var y1 = val[1][0][1] / 2.0;
var x2 = val[1][2][0] / 2.0;
var y2 = val[1][2][1] / 2.0;
draw_text([0,0], [(x1 + x2) / 2.0, (y1 + y2) / 2.0], nearestRegion)
}
})
} else {
nearestRegion = null;
}
}
// https://stackoverflow.com/questions/17097892/clicking-text-in-an-html5-canvas
function changeCursor(e) {
if (nearestRegion) {
document.body.style.cursor = "pointer";
} else {
document.body.style.cursor = "default";
}
e.preventDefault();
}
function triggerClickDialog(e) {
let isMobile = window.matchMedia("only screen and (max-width: 760px)").matches;
if (!isMobile) {
triggerDialog(e);
}
}
function triggerDialog(e) {
var pos = getMousePos(e);
if (pos.x && pos.y) {
if (nearestRegion) {
var ret = in_region([pos.x / (SCALE_FACTOR * GS), pos.y / (SCALE_FACTOR * GS)], 3);
if (ret.length !== 0) {
ret.map(function (val) {
if (val[0] !== "wall") {
showDialog();
}
})
} else {
moveTo = [pos.x / (GS * SCALE_FACTOR), pos.y / (GS * SCALE_FACTOR)]
playerMoving = DIRECTIONS["MOUSE"]
}
} else {
moveTo = [pos.x / (GS * SCALE_FACTOR), pos.y / (GS * SCALE_FACTOR)]
playerMoving = DIRECTIONS["MOUSE"]
}
}
}
// https://stackoverflow.com/questions/41993176/determine-touch-position-on-tablets-with-javascript#41993300
function getMousePos(e) {
var x, y;
if(e.type == 'touchstart' || e.type == 'touchmove' || e.type == 'touchend' || e.type == 'touchcancel'){
var touch = e.touches[0] || e.changedTouches[0];
x = touch.pageX;
y = touch.pageY;
} else if (e.type == 'mousedown' || e.type == 'mouseup' || e.type == 'mousemove' || e.type == 'mouseover'|| e.type=='mouseout' || e.type=='mouseenter' || e.type=='mouseleave' || e.type =='click') {
x = e.clientX;
y = e.clientY;
}
var padding = (screen.width - canvas.width) / 2.0;
return {
x: x - padding,
y: y
};
}
function showDialog() {
var dialog;
if (nearestRegion == "wall") {
} else if (nearestRegion === "code library") {
dialog = `<iframe
frameborder="0"
marginwidth="0"
marginheight="0"
src="/literate-programming.html"
height=${screen.height - 16}
width=${screen.width - 16}
>
</iframe>
`;
} else if (nearestRegion === "jukebox") {
// https://stackoverflow.com/questions/4907843/open-a-url-in-a-new-tab-and-not-a-new-window
const link = document.createElement('a');
link.href = "https://xyzzyapps.link/my-music";
link.target = '_blank';
document.body.appendChild(link);
link.click();
link.remove();
} else if (nearestRegion === "demos") {
dialog = `
<iframe
width="560" height="315"
src="https://www.youtube.com/embed/videoseries?list=PLbyPZ-v56IPnTDISg3pFUYl7374q7jqsq"
title="Demos"
frameborder="0"
allowfullscreen>
</iframe>
`;
} else if (nearestRegion === "xyzzy") {
dialog = `<p>
Hi!<br>
Mail me at xyzzyapps@gmail.com for chat!
</p>
`;
}
if(dialog) {
var opts = {
lines: 13, // The number of lines to draw
length: 38, // The length of each line
width: 17, // The line thickness
radius: 18, // The radius of the inner circle
scale: 1, // Scales overall size of the spinner
corners: 1, // Corner roundness (0..1)
speed: 1, // Rounds per second
rotate: 0, // The rotation offset
animation: 'spinner-line-fade-quick', // The CSS animation name for the lines
direction: 1, // 1: clockwise, -1: counterclockwise
color: '#ffffff', // CSS color or array of colors
fadeColor: 'transparent', // CSS color or array of colors
top: '50%', // Top position relative to parent
left: '50%', // Left position relative to parent
shadow: '0 0 1px transparent', // Box-shadow for the lines
zIndex: 2000000000, // The z-index (defaults to 2e9)
className: 'spinner', // The CSS class to assign to the spinner
position: 'absolute', // Element positioning
};
spinner = new Spinner(opts).spin(document.body);
var spinner;
let dialogBox = xdialog.create({
title: null,
body: dialog,
buttons: ["ok"],
aftershow: function () {
spinner.stop();
},
style: "",
});
dialogBox.show();
}
}
function draw() {
window.SCALE_FACTOR = Math.round(((screen.height / 720.0) * 100) + 1) / 100;
speed = 8 * SCALE_FACTOR / GS; // in terms of relative positioning of grid
playerPosition = [5, 15];
canvas.height = screen.height;
canvas.width = canvas.height * RATIO;
var padding = (screen.width - canvas.width) / 2.0;
canvas.style = `padding-left: ${padding}px; padding-right: ${padding}px"`;
document.getElementById("social").style.paddingLeft = `${padding}px`;
// var musicList = [
// { name : "field", url: "music/field" },
// { name : "castle", url: "music/castle" }
// ];
// var audioObj = new Audio();
// if (audioObj.canPlayType("audio/mp3") == "maybe") { var ext = ".mp3"; }
// (new Audio(musicList[bgmNo].url + ext)).play();
canvas.addEventListener('click', triggerClickDialog, false);
canvas.addEventListener('touchend', triggerDialog, false);
canvas.addEventListener('mousemove', changeCursor, false);
document.onkeydown = function(e) {
activeKey = e.which;
if (e.keyCode == 32) {
showDialog();
} else if (activeKey == 37) {
playerMoving = DIRECTIONS["LEFT"]
} else if (activeKey == 38) {
playerMoving = DIRECTIONS["UP"]
} else if (activeKey == 39) {
playerMoving = DIRECTIONS["RIGHT"]
} else if (activeKey == 40) {
playerMoving = DIRECTIONS["DOWN"]
}
e.preventDefault();
}
document.onkeyup = function(e) {
activeKey = null;
playerMoving = -1;
e.preventDefault();
}
interval = setInterval('mainLoop()', 16);
}
map.js¶
The following apps were used for images
Tiled
https://www.photopea.com/
https://pixel-me.tokyo/
https://make8bitart.com/
Use convert images/map.bmp images/map.png
for converting the image.
var map = new Image();
map.src = "images/map.png";
var player = new Image();
player.src = "images/player.png";
var xyzzy = new Image();
xyzzy.src = "images/soldier.png";
function draw_image( offset) {
ctx.drawImage(
map,
0, 0,
1260, 720,
0, 0,
1260 * SCALE_FACTOR, 720 * SCALE_FACTOR // destination height, width
);
}
function draw_xyzzy() {
var no = 0;
var px = 34;
var py = 2;
ctx.drawImage(
xyzzy,
no * GS, 0, // sub rectangle x,y
GS, GS, // sub rectangle height, width
px * GS * SCALE_FACTOR, py * GS * SCALE_FACTOR, // destination x,y
GS * SCALE_FACTOR, GS *SCALE_FACTOR // destination height, width
);
}
function draw_character( offset, direction, position) {
var animcycle = 12;
var offsetx = offset[0];
var offsety = offset[1];
if (direction !== -1) {
var no = div(FRAME, animcycle) % 4;
} else {
var direction = 0;
var no = 0;
}
var px = position[0] * GS * SCALE_FACTOR;
var py = position[1] * GS * SCALE_FACTOR;
ctx.drawImage(
player,
no* GS, direction * GS, // sub rectangle x,y
GS, GS, // sub rectangle height, width
px-offsetx, py-offsety, // destination x,y
GS * SCALE_FACTOR, GS * SCALE_FACTOR // destination height, width
);
}
function draw_text( offset, position, text) {
var font_size = 26;
ctx.font = font_size + 'px FourThreeSeven';
var width = ctx.measureText(text).width;
var padding = 0.5 * GS * SCALE_FACTOR;
var bubble_width = width + padding;
var rectangle = new Path2D();
rectangle.fillStyle = 'white';
rectangle.strokeStyle = 'black';
rectangle.rect(position[0] * GS * SCALE_FACTOR - padding, position[1] * GS * SCALE_FACTOR - 2.5 * padding, width + 2 * padding, font_size + 2 * padding);
ctx.stroke(rectangle);
ctx.fill(rectangle);
ctx.fillStyle = 'black';
ctx.fillText(text, position[0] * GS * SCALE_FACTOR, position[1] * GS * SCALE_FACTOR)
ctx.fillStyle = 'white';
}
index.html¶
Mobile bug - https://stackoverflow.com/questions/10866976/mouse-click-or-touch-events-on-canvas-causes-selection-using-html5-phonegap-a
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta id="viewport_meta" name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/js/xdialog.min.css">
<link rel="stylesheet" href="images/spin.css">
<link rel="manifest" href="manifest.json">
<title>Xyzzy's Home</title>
<style>
html, body, canvas {
-webkit-backface-visibility: hidden;
overflow: hidden;
font-family: 'FourThreeSeven';
font-size: 16px;
margin: 0px;
border: 0px;
padding: 0px;
-webkit-user-select: none;
-khtml-user-select: none;
-webkit-tap-highlight-color: transparent;
-moz-user-select: none;
-o-user-select: none;
user-select: none;
}
@font-face {
font-family: 'FourThreeSeven';
font-style: normal;
font-weight: 400;
src: local('FourThreeSeven'), url(images/FourThreeSevenMedium.ttf) format('truetype');
}
.xd-content .xd-body {
overflow: hidden !important;
}
.xd-body-inner {
overflow-y: scroll !important;
overflow-x: hidden !important;
}
.social img {
width: 32px;
height: 32px;
}
@media screen and (max-width: 1259px ) {
.social img {
width: 16px;
height: 16px;
}
}
</style>
</head>
<body tabindex="-1">
<div id="social" style="position: absolute; top: 2px; left: 4px;" class="social" tabindex="-1">
<a target="_blank" href="https://fossil.xyzzyapps.link"><img src="images/Fossil.png"></a>
<a target="_blank" href="https://github.com/xyzzyapps"><img src="images/Github.png"></a>
<a target="_blank" href="https://twitter.com/xyzzyapps"><img src="images/Twitter.png"></a>
<a target="_blank" href="https://twitch.com/xyzzyapps"><img src="images/Twitch.png"/></a>
<a target="_blank" href="https://blog.xyzzyapps.link/"><img src="images/Wordpress.png"/></a>
<a target="_blank" href="https://www.youtube.com/channel/UCFzQ95TGgFF2CmMmp8dv40Q"><img src="images/Youtube.png"/></a><br>
<a target="_blank" href="https://xyzzyapps.substack.com/">my newsletters</a><br>
<a target="_blank" href="https://github.com/xyzzyapps/xyzzyapps.link">issues ?</a>
</div>
<div id="portrait" style="display: none; color: white; position: absolute; top: 20vh; left: 0.5vw">
Please rotate screen
</div>
<canvas id="canvas" width="1260" height="720" tabindex="-1">
Please use the web browser compatible with HTML5.
</canvas>
</body>
<script src="js/xdialog.min.js"></script>
<script src="js/spin.js"></script>
<script type="text/javascript" src="js/draw.js"></script>
<script type="text/javascript" src="js/main.js"></script>
<script type="text/javascript" async="" src="//blog.xyzzyapps.link/oozotche/matomo/matomo.js"></script><script type="text/javascript">
var _paq = window._paq = window._paq || [];
_paq.push(['trackPageView']);_paq.push(['enableLinkTracking']);_paq.push(['alwaysUseSendBeacon']);_paq.push(['setTrackerUrl', "\/\/blog.xyzzyapps.link\/owoamims\/matomo\/app\/matomo.php"]);_paq.push(['setSiteId', '1']);var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.src="\/\/blog.xyzzyapps.link\/oozotche\/matomo\/matomo.js"; s.parentNode.insertBefore(g,s);
</script>
</html>