Compare commits
No commits in common. 'develop' and 'master' have entirely different histories.
@ -1,216 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="A list of items that live in Aney's bedroom">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="black">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>My Bedroom Belonging</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1>Aney's Bedroom</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<section>
|
|
||||||
<h2>My Bedroom Belongings</h2>
|
|
||||||
|
|
||||||
<p>The items that typically live in my bedroom, assuming I'm currently staying in the house.</p>
|
|
||||||
|
|
||||||
<section id="bed">
|
|
||||||
<h3>Bed</h3>
|
|
||||||
<p>My bed, made up of a base, memory foam topped mattress, a duvet, two pillows, and a headboard.</p>
|
|
||||||
|
|
||||||
<h4>Atop the bed:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Buc-ee's throw</li>
|
|
||||||
<li>Pyjama bottoms</li>
|
|
||||||
<li>Croca (my teddy crocodile)</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>On/around the headboard:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Reading lamp/torch</li>
|
|
||||||
<li>Sleeping mask</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h3>Bedside Cabinet</h3>
|
|
||||||
<p>A generic 3 tiered bedside cabinet my Grandad gave me.</p>
|
|
||||||
|
|
||||||
<h4>Atop the cabinet:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Lamp</li>
|
|
||||||
<li>Current book I'm reading</li>
|
|
||||||
<li>Croca (my teddy crocodile)</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>First drawer:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Pocket notebook(s)</li>
|
|
||||||
<li>Travel Toiletry bag (with any toiletries not currently in use)</li>
|
|
||||||
<li>Hayfever/wet wipes</li>
|
|
||||||
<li>Tablets, vitamins, beard cream, chewing gum</li>
|
|
||||||
<li>Grip strengthener</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>Second drawer:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>1x pair of jeans</li>
|
|
||||||
<li>2x t-shirts</li>
|
|
||||||
<li>1x pyjama bottoms</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>Third drawer:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>2x bamboo socks</li>
|
|
||||||
<li>2x bombas socks</li>
|
|
||||||
<li>2x bamboo underwear</li>
|
|
||||||
<li>Merino wool gloves</li>
|
|
||||||
<li>Merino wool buff</li>
|
|
||||||
<li>Wooly hat</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>Beneath:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Rpi5 8GB **</li>
|
|
||||||
<li>240GB SSD (with USB to Sata adapter) **</li>
|
|
||||||
<li>Router **</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h3>Hanger</h3>
|
|
||||||
<p>This is a small 4 knobbed hanger attached to the boiler door.</p>
|
|
||||||
<ul>
|
|
||||||
<li>2x Jumpers</li>
|
|
||||||
<li>Sling/Bumbag *</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h3>Cabinet</h3>
|
|
||||||
<p>A medium sized four tiered plastic cabinet, it cost ~£10.</p>
|
|
||||||
|
|
||||||
<h4>Atop:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Pencil case *</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>First drawer:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>SAD Lamp</li>
|
|
||||||
<li>Small sewing kit</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>Second drawer:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>7.5m auto locking tape</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>Third drawer:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Skipping rope</li>
|
|
||||||
<li>Second monitor, and stand</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>Fourth drawer:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Jigsaw (puzzle)</li>
|
|
||||||
<li>Rubicks Cube</li>
|
|
||||||
<li>Light ankle weights</li>
|
|
||||||
<li>3x medium packing cubes</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h3>Makeshift Table</h3>
|
|
||||||
<p>Next to the plastic draw, a 'table' constisting of two stools with a 1U server chassic atop.</p>
|
|
||||||
|
|
||||||
<h4>Atop:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Pencil holder</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>Beneath:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Small rubbish bin</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h3>Workspace</h3>
|
|
||||||
<p>This sits near the window, where I can get sunlight while staring at a screen.</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Foldable desk</li>
|
|
||||||
<li>Foldable chair</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>When these are not in use, they're folded up and put behind the 'table', and plastic cabinet.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h3>Other/Misc.</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Dip bar</li>
|
|
||||||
<li>TP-Link Powerline Adapter</li>
|
|
||||||
<li>Scales (No battery)</li>
|
|
||||||
<li>Backpack</li>
|
|
||||||
<li>Soundcore Mini 3 **</li>
|
|
||||||
<li>Kovebble Bluetooth Headphones **</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>When these are not in use, they're folded up and put behind the 'table', and plastic cabinet.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>Stuff that moves around</h2>
|
|
||||||
<p>I have a few things that don't have a 'home', but follow me where I am spending my time, whether at my desk(s), in bed, or in my pocket if I'm not in the room.</p>
|
|
||||||
<ul>
|
|
||||||
<li>Water bottle</li>
|
|
||||||
<li>Phone, and phone stand</li>
|
|
||||||
<li>Bullet Journal (and pen)</li>
|
|
||||||
<li>Inhaler(s)</li>
|
|
||||||
<li>Allergy tablets</li>
|
|
||||||
<li>Toilet roll</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<p>(*) Has more detail, but I'll likely add that to my 'OneBag' page when I get around to writing that.</p>
|
|
||||||
<p>(**) Listed elsewhere on the site.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="A new step for aney.co.uk, there's now a blog.">
|
|
||||||
<meta name="keywords" content="Blog, articles, news">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="black">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>I'm going to America</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1>I'm going to America</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<section>
|
|
||||||
<p>Hey, hey. Quick update, I'm travelling to, and visiting America to see my parents.</p>
|
|
||||||
|
|
||||||
<h2>Whatchu gonna do?</h2>
|
|
||||||
<p>I'll still be working, as I WFH, so I'll be enjoying 3AM starts whilst in the US.</p>
|
|
||||||
<p>Other than working, we'll just be spending time together. There are plans for a week in Universal Studios, so I'm looking forwards to that!</p>
|
|
||||||
|
|
||||||
<h2>What about your project?</h2>
|
|
||||||
<p>Work will still be done on it, but very slowly, and I may forget where I was with the codebase by the time I get back, so that's going to slow it down even more.</p>
|
|
||||||
|
|
||||||
<h2>So no devlogs?</h2>
|
|
||||||
<p>I have some content worth writing up already done, and almost done (in codebase, not written into posts), so there likely will still be devlogs.</p>
|
|
||||||
<p>If I do write these up, there will only be one a week (keeping friday), and they will be posted much later in the day, maybe even the next day.</p>
|
|
||||||
<p>I did say I'd be keeping Friday, but potentially next Friday will be skipped (or posted on Monday), as I will be jetlagged when I arrive, and will be working throughout the first week I'm in the US, beginning the literal day after I arrive (which gives me 2 hours to spend with parents upon the airport landing, then I need to hit the hay).</p>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="green">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>Project 1: Basic Drawing and Interaction</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1 style="font-size: 2.4rem">Project 1: Basic Drawing and Interaction</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<p>Switching away from the server-side stuff after getting the stack setup, I've opted to the basic game 'drawn', and 'playable', if you can call it those.</p>
|
|
||||||
|
|
||||||
<h2>Drawing</h2>
|
|
||||||
<p>Since I'm using JS and HTML canvas I need to draw each shape onto the canvas setting each individual shapes co-ordinates and size. This sounds simple, but the more shapes you draw, and the more you don't want them to overlap, or to add zoomed in/out shapes it gets much more complicated than you'd think. This is a prime example of why a solid grasp on basic mathematics is good for developers.</p>
|
|
||||||
|
|
||||||
<h3>Shape class</h3>
|
|
||||||
<p>Repeating the setting of context data, and the drawing of each shape became tedious and took a big chunk of my code-base after not long, so I decided to simplify by creating a Path2D extended class.</p>
|
|
||||||
<pre class="pre--small"><code>class Shape extends Path2D{
|
|
||||||
constructor(params){
|
|
||||||
super();
|
|
||||||
this.shape = params.shape || 'rectangle';
|
|
||||||
this.name = params.name;
|
|
||||||
this.x = params.x || 0;
|
|
||||||
this.y = params.y || 0;
|
|
||||||
this.width = params.width || 0;
|
|
||||||
this.height = params.height || 0;
|
|
||||||
this.fillStyle = params.fillStyle || "#FFFFFF";
|
|
||||||
}
|
|
||||||
draw(){
|
|
||||||
context.fillStyle = this.fillStyle;
|
|
||||||
if(this.shape == 'circle'){
|
|
||||||
context.beginPath();
|
|
||||||
context.arc(this.x, this.y, this.width/2, 0, 2 * Math.PI);
|
|
||||||
context.fill();
|
|
||||||
context.closePath();
|
|
||||||
}else if(this.shape == 'semi'){
|
|
||||||
context.beginPath();
|
|
||||||
context.arc(this.x, this.y, this.width/2, Math.PI, 0);
|
|
||||||
context.fill();
|
|
||||||
context.closePath();
|
|
||||||
}else if (this.shape == 'rectangle'){
|
|
||||||
context.fillRect(this.x, this.y, this.width, this.height);
|
|
||||||
}
|
|
||||||
context.fillStyle = defaultFillStyle; // Reset back to default
|
|
||||||
}
|
|
||||||
}</code></pre>
|
|
||||||
<p>Pretty simple, I pass the shape I want to draw, x,y co-ordinates, width and height, and the fill. Not included above, but the class also accepts, and draws the shape stroke.</p>
|
|
||||||
<p>I then call the draw method of the new Shape, and it sorts everything for me.</p>
|
|
||||||
|
|
||||||
<h2>Interaction with Event Handlers</h2>
|
|
||||||
<p>Next up, for the game to be a game and not just some visual media, we need to be able to interact with it.</p>
|
|
||||||
<p>Basic interaction is simple enough, I threw in a 'click' EventListener, then take the event/click's X/Y position and subtract the canvas's offset from the HTML body (to have 0,0 set to the top-left of the canvas, and not the HTML page).</p>
|
|
||||||
<p>This new X/Y, which I will just call X/Y is then compared to the X/Y locations of each shape. If the event X/Y occurs in a location greater (or equal) than the shape X/Y, and less than (or equal) Shape X/Y plus the shape Width/Height I then do whatever action I require.</p>
|
|
||||||
<p>This is another thing I've simplified by creating a function:</p>
|
|
||||||
<pre class="pre--small"><code>function clickableCheck(x,y,shape){
|
|
||||||
if(
|
|
||||||
y > shape.y && y < shape.y + shape.height
|
|
||||||
&& x > shape.x && x < shape.x + shape.width)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}</code></pre>
|
|
||||||
<p>The function above is even simpler too, as all you need to do to check for a click, is pass a Shape object, created by the Shape class earlier in this post!</p>
|
|
||||||
<p>Fun Fact! Shape here, is actually called clickable in my code-base, but I've changed it to make more sense without context.</p>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,184 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="green">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>Project 1: Basic Mechanic Hack In</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1 style="font-size: 2.4rem">Project 1: Basic Mechanic Hack In</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<p>With the basic mechanics now thought of thanks to my research, and constantly evolving <a href="/blog/p1-spreadsheet-gdd.html">Spreadsheet GDD</a>, I can now add them (in their most basic sense) into my game!</p>
|
|
||||||
<p>Disclaimer! Anything written in this article is not indicative of how the mechanic will work in it's final stages. The code examples here are also specifically examples of the hackyness of this stage, and extremely far from what the codebase will be.</p>
|
|
||||||
|
|
||||||
<h2>Still developing on front-end</h2>
|
|
||||||
<p>For ease, and speed of development I opted to continue the development work front-end to benefit from console logs, and alert pop-ups when a condition that shouldn't get hit, gets hit. I also don't have to think about any data to be sent, so can happily hack away changing everything at a whim.</p>
|
|
||||||
|
|
||||||
<h2>'Basic' Mechanics?</h2>
|
|
||||||
<p>My game has a number of mechanics, each with more complexity than is 'needed' to have a playable game, this complexity is what will allow me to balance, and create distinctions between similar items.</p>
|
|
||||||
<p>Keeping this in mind, I just need a somewhat playable experience that uses the absolute basics of my core/fundamental mechanics so I can get a big boost in progress of making the game itself. The complexity will be added later on, once the codebase exists, has been tidied up, and made a bit more efficient (foreshadowing for a future article, how does he do it).</p>
|
|
||||||
|
|
||||||
<h2>The Hack-in</h2>
|
|
||||||
<p>Since my only concern at this point was to get the mechanics in, and make each of the most basic 'playable' moves I added very basics.</p>
|
|
||||||
<ul>
|
|
||||||
<li>Summoning units</li>
|
|
||||||
<li>Adding shield, and 'resource'</li>
|
|
||||||
<li>Attacking with units</li>
|
|
||||||
<li>Inspecting oppossing units</li>
|
|
||||||
<li>Basic one-dimensional win condition</li>
|
|
||||||
<li>Preventing interaction during 'special events', such as during an attack</li>
|
|
||||||
</ul>
|
|
||||||
<p>These are my current mechanics (still attempting to be vague), which I believe I'm mostly settled on, likely with only minor changes to the limits and conditions for each field location. When the game's closer to a testing phase I'll do a full writeup of each.</p>
|
|
||||||
|
|
||||||
<h2>Summon a unit</h2>
|
|
||||||
<p>Once a unit is available to be played a check is done to see if there is enough 'resource' to add it to the field, for the hack-in I've only done a requirement of one per unit, but it does need to be of the same 'alliedType'.</p>
|
|
||||||
<pre class="pre--small"><code>playCardToField(index){
|
|
||||||
let UnitPlayed = availableUnits[index];
|
|
||||||
|
|
||||||
// Check if there's space to add unit
|
|
||||||
if(unitArea.length >= maxUnits){
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(UnitPlayed.cost > playerResource.length){
|
|
||||||
return 0;
|
|
||||||
}else{
|
|
||||||
let canPlay = false;
|
|
||||||
let needsResource = 1;
|
|
||||||
let usedResource = 0;
|
|
||||||
playerResource.forEach(function(ResourceUnit, key){
|
|
||||||
if(UnitPlayed.alliedType == ResourceUnit.alliedType && ResourceUnit.cantAttack == false && needsResource > usedResource)
|
|
||||||
{
|
|
||||||
ResourceUsed.push(key);
|
|
||||||
usedResource++;
|
|
||||||
canPlay = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!canPlay){
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark resource as used (so they can't be used for another unit)
|
|
||||||
ResourceUsed.forEach(function(UnitKey, key){
|
|
||||||
playerResource[UnitKey].used = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove from playable units
|
|
||||||
availableUnits.splice(index, 1);
|
|
||||||
// Add unit to field
|
|
||||||
unitArea.push(UnitPlayed);
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
|
|
||||||
<p>This code is essentially the same for adding shields, and 'resource' too, just with a few minor changes, as the hack-in mode in me let me just straight copy-and-paste functions.</p>
|
|
||||||
|
|
||||||
<h2>Special Events + Check</h2>
|
|
||||||
<p>A Special event, is what I ended up naming events that have specific options to progress/end such as 'Attacking' and 'Inspecting' which I used for testing in the hack-in.</p>
|
|
||||||
<p>If the game has been won, or there is a unit being inspected, or attacking a variable within the eventListeners is set.</p>
|
|
||||||
<pre class="pre--small"><code>let specialEvent = false;
|
|
||||||
if(inspectUnit !== null || attackingUnit !== null || gameWin){
|
|
||||||
specialEvent = true;
|
|
||||||
}
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>This variable is then checked when a certain target is selected, and depending on if specialEvent is true (if so it typically will check for the correct event, i.e. attacking as below) it'll trigger a different function call, or if false just prevent a function call.</p>
|
|
||||||
<pre class="pre--small"><code>playerField.forEach(function(item, index){
|
|
||||||
let clickable = item.clickable;
|
|
||||||
|
|
||||||
if(clickableCheck(x,y,clickable)){
|
|
||||||
if(attackingUnit !== null && item == attackingUnit[0]){
|
|
||||||
endAttack();
|
|
||||||
}
|
|
||||||
if(!specialEvent && item.cantAttack != true){
|
|
||||||
startAttack(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});</code></pre>
|
|
||||||
<p>The attacking/inspectingUnit, event data is set when one is triggered, explained in the text below.</p>
|
|
||||||
|
|
||||||
|
|
||||||
<h2>Attacking</h2>
|
|
||||||
<p>Attacking is split into three phases.</p>
|
|
||||||
<p>When an available unit is selected, it sets that unit in the 'attacking' variable in startAttack().</p>
|
|
||||||
<pre class="pre--small"><code>startAttack(index){
|
|
||||||
attackingUnit = [playerField[index], index];
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
<p>If player then selects an opposing shield or unit, while an 'attacking' unit is set, the makeAttack() function passes the (jank incoming) array, arrayName, and the targets array index to perform checks.</p>
|
|
||||||
<p>If a shield is destroyed, the opposing player gets whatever unit was used as a shield added to their 'availableUnits', as a catch-up mechanic.</p>
|
|
||||||
<p>If units are of equal power, they are both unalived, if one is stronger they get to live, while the other, not so much.</p>
|
|
||||||
<pre class="pre--small"><code>makeAttack(index, array = null, name = null){
|
|
||||||
if(array == null){ array = opponentField; name = 'opponentField' }
|
|
||||||
let defendingUnit = array[index];
|
|
||||||
|
|
||||||
// If hitting shield, don't calc combat damage
|
|
||||||
if(name == 'opponentShield'){
|
|
||||||
if(array[index].tapped){
|
|
||||||
array[index].tapped = false;
|
|
||||||
opponentAvailableUnits.push(array[index]);
|
|
||||||
array.splice(index, 1);
|
|
||||||
}else{
|
|
||||||
array[index].tapped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
playerField[attackingUnit[1]].tapped = true;
|
|
||||||
this.endAttack();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do calc combat damage for each unit if unit vs unit fight
|
|
||||||
if(defendingUnit.atk <= attackingUnit[0].atk){
|
|
||||||
array.splice(index, 1);
|
|
||||||
}
|
|
||||||
if(attackingUnit[0].atk <= defendingUnit.atk){
|
|
||||||
playerField.splice(attackingUnit[1], 1);
|
|
||||||
}else{
|
|
||||||
playerField[attackingUnit[1]].tapped = true;
|
|
||||||
}
|
|
||||||
this.endAttack();
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
|
|
||||||
<p>Once the makeAttack() has completed, it then calls endAttack(), which sets the 'attacking' variable to null that will allow non-specific interactions again, then checks the opposing player's shield, if it's destroyed the gameWin state is set. endAttack() can also be called by reselecting the 'attacking' unit, as a means to not force players into combat.</p>
|
|
||||||
<pre class="pre--small"><code>endAttack(){
|
|
||||||
attackingUnit = null;
|
|
||||||
if(opponentShield.length <= 0){
|
|
||||||
gameWin = 1;
|
|
||||||
}
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
<p>That's all. My vagueness remains for the time being (inc. in the code examples, as I've changed variable names for example), I only hope that I'm not getting people excited for a gametype that isn't what I'm making. I need to keep the vagueness thoguh, as once I annouce what it is I will feel a mix of pressure, and pre-success that I believe will detriment my development, and potentially get me to quit before completion.</p>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,312 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="green">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>Project 1: ECSey Code Restructure</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1 style="font-size: 2.4rem">Project 1: ECSey Code Restructure</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<p>Believe me when I say, the <a href="/blog/p1-basic-mechanic-hack-in.html">hack in</a> code hurts me more than it hurts you. With that said, it was essential to getting the core in, and can easily be re-written, which I started on immediately after it's completion.</p>
|
|
||||||
|
|
||||||
<h2>Data Oriented?</h2>
|
|
||||||
<p>While creating the core of the game, even during the hack in I had a somewhat data-oriented approach, with the use of arrays, and objects of pure data scattered around rather than classes that could bulk and slow the game down.</p>
|
|
||||||
<p>As it was hacky though, it eventially become that there were too many arrays with too much similar data, that were accessed slightly differently.</p>
|
|
||||||
<p>This led me to look into slowly writting the code base over, replacing individual arrays and functions with more universal data, with each piece of data only being referrenced once, and any functionality directly working with that data.</p>
|
|
||||||
|
|
||||||
<h2>ECSey</h2>
|
|
||||||
<p>With my mind in a semi data oriented mode, I thought of other games (and performant required software) and how they would typically implement an ECS (Entity Component System), and I opted to go the same route.</p>
|
|
||||||
<p>I have never written an ECS, and didn't look into it, as I vaguely understood what's what and the purpose. Since I did this however I'm unsure if I ended up with a 'true ECS', so I opted to refer to my codebase as 'ECSey'.</p>
|
|
||||||
<p>Here's how it works, mostly.</p>
|
|
||||||
|
|
||||||
<h3>The Breakdown</h3>
|
|
||||||
<h4>Entity</h4>
|
|
||||||
<p>The 'object' itself, these are stored in an 'item' array, and are stored only as an integer, no other data at all.</p>
|
|
||||||
<pre class="pre--small"><code>item = [0,1,2,3,4];</code></pre>
|
|
||||||
<h4>Component</h4>
|
|
||||||
<p>The 'attributes' of the Entity. These contain the actual data for each of the 'objects'/entities. They are stored in an array (an object in JS) and are accessed by referring to said array with the key of the 'entity'/item ID the attribute belongs to.</p>
|
|
||||||
<pre class="pre--small"><code>size[entityId] = [width, height]; position[itemKey] = [positionX,positionY];</code></pre>
|
|
||||||
<h4>System</h4>
|
|
||||||
<p>Each system consists of the function calls, and processes that access and perform actions with the entity's attributes, iterating each of them and performing the actions according to their data. So, for instance I have a 'drawItems' function that loops each item, and draws that item to the board based on the entity's size, and position (if it has then both set).</p>
|
|
||||||
|
|
||||||
|
|
||||||
<h2>ECSey code examples</h2>
|
|
||||||
<p>And here are some snippets of what the new codebase looks like at current. Still not finalised, but so much better.</p>
|
|
||||||
|
|
||||||
<h3>Basic Components</h3>
|
|
||||||
<p>As this is the first pass for the ECSey design, I replicated the existing arrays and objects into components/attributes. This is not all there will be in the future, in fact this list has more than doubled already!</p>
|
|
||||||
<pre class="pre--small"><code>let item = [];
|
|
||||||
let itemCount = 0; // Used to keep track of how many items there have been
|
|
||||||
|
|
||||||
let boardElement = {};
|
|
||||||
let cardData = {};
|
|
||||||
let position = {};
|
|
||||||
let size = {};
|
|
||||||
let cardStatus = {};
|
|
||||||
let player = {};
|
|
||||||
let listPosition = {};</code></pre>
|
|
||||||
|
|
||||||
<h3>Get Items</h3>
|
|
||||||
<p>This loop all the entities, compares the passed 'boardElement', 'playerId', 'cardStatus', 'listPosition', and any other attributes passed, and returns a new array of only the entities that match the criteria.</p>
|
|
||||||
<pre class="pre--small"><code>getItems(playerId = null, boardElementId = null, ... ){
|
|
||||||
|
|
||||||
let newItems = item; // Set array to all items (item is current name of entities array)
|
|
||||||
let tempArray = []; // New array for filtering
|
|
||||||
|
|
||||||
// Check if each item shares the PLAYERID passed
|
|
||||||
if(playerId !== null){ // Only check if a playerId has been passed to getItems
|
|
||||||
|
|
||||||
for(let newItem = 0; newItem < newItems.length; newItem++){
|
|
||||||
|
|
||||||
let itemKey = newItems[newItem]; // Get the entity key/ID
|
|
||||||
|
|
||||||
// If the item shares the playerId, add it to the tempArray
|
|
||||||
if(playerId == player[itemKey]){
|
|
||||||
tempArray.push(newItems[newItem]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set newItems to tempArray so it can be looped again with only what matched
|
|
||||||
newItems = tempArray;
|
|
||||||
}
|
|
||||||
// Reset tempArray so it can be reused in next loop
|
|
||||||
tempArray = [];
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
// Return the new specified itemList
|
|
||||||
return newItems;
|
|
||||||
</code></pre>
|
|
||||||
<p>Each of the components passed gets checked and looped in the same manner, slowly filtering out what's not wanted. Then the newItems array is returned to be used however needed.</p>
|
|
||||||
|
|
||||||
<h3>Get Item Key</h3>
|
|
||||||
<p>Sometimes only a single item is wanted. This can be retried by passing the three core components I have in the game to date.</p>
|
|
||||||
<pre class="pre--small"><code>getItemKey(boardElementId, listPositionId, playerId){
|
|
||||||
|
|
||||||
// This calls getItems from example above, passing the component data
|
|
||||||
let itemKey = this.getItems(boardElementId, playerId, null, listPositionId);
|
|
||||||
|
|
||||||
if(itemKey.length < 1){ return false; } // Didn't find a key
|
|
||||||
if(itemKey.length > 1){ return false } // Found more than 1 key
|
|
||||||
|
|
||||||
// Return first index of array, aka the itemKey
|
|
||||||
return itemKey[0];
|
|
||||||
}</code></pre>
|
|
||||||
<p>All this does is call getItems, does a little error handling and returns the first (only) entitiy.</p>
|
|
||||||
<p>This will only return one, unless something gets broken elsewhere, as each player's boardElements have unique 1..X listPositions that get moved up/down when something is added/removed from the boardElement. There are also error messages, but I've ommited them from the snippet.</p>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3>Playing to boardElement, Board/Mana/Shield</h3>
|
|
||||||
<p>To demonstrate the fact that getItemKey() will only return one entity we'll look at how I switch entities between the 'boardElements'</p>
|
|
||||||
<pre class="pre--small"><code>addFromBoardElement(playerFrom, fromPosition, elementFrom, elementTo, toPosition=null, playerTo=null){
|
|
||||||
|
|
||||||
// If no playerTo provided (typical behavior), pass between elements for same player
|
|
||||||
if(playerTo == null){ playerTo = playerFrom; }
|
|
||||||
|
|
||||||
// Check there is only 1 item that exists with the from info
|
|
||||||
let itemKey = this.getItemKey(elementFrom, fromPosition, fromPlayer);
|
|
||||||
|
|
||||||
// Check if there is a specific position the item needs to go to
|
|
||||||
if(toPosition == null){
|
|
||||||
// If not get the next available position of the elementTo
|
|
||||||
toPosition = getCurrentPositionAndLength(elementTo, playerTo)[0]+1;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setCardPosition(itemKey, toPosition, elementTo, playerTo, fromPosition, elementFrom, playerFrom);
|
|
||||||
this.removeItemStatus(itemKey); // Untap, remove 'attacking' etc.
|
|
||||||
|
|
||||||
}</code></pre>
|
|
||||||
<p>setCardPosition called first decreases the listPosition of anything from the old boardElement after the old listPosition by 1. It then increases the listPositions in the new boardElement up by one for anything equal to or higher than the new position.</p>
|
|
||||||
<p>Don't worry if it doesn't make sense at first, I wrote it and it took me a while of writing arrays in a physical notebook before I was sure what would work.</p>
|
|
||||||
<pre class="pre--small"><code>setCardPosition(card, newPosition, newElement, newPlayer, oldPosition, oldElement, oldPlayer){
|
|
||||||
|
|
||||||
// Move old boardElement listPositions down
|
|
||||||
this.moveElementPositions(0, oldElement, oldPosition, oldPlayer);
|
|
||||||
|
|
||||||
// Move new boardElement listPositions up
|
|
||||||
this.moveElementPositions(1, newElement, newPosition, newPlayer);
|
|
||||||
|
|
||||||
// Then fit the card into the new gap that's opened up in new boardElement
|
|
||||||
listPosition[card] = newPosition;
|
|
||||||
boardElement[card] = newElement;
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
<p>This is what moveElementPositions does.</p>
|
|
||||||
<pre class="pre--small"><code>moveElementPositions(direction, elementFrom, fromPosition, playerFrom){
|
|
||||||
|
|
||||||
// Loop the elementFrom, and move positions up/down by one
|
|
||||||
let items = this.getItems(elementFrom, playerFrom, null, null);
|
|
||||||
|
|
||||||
for(let item = 0; item < items.length; item++){
|
|
||||||
let itemKey = items[item];
|
|
||||||
|
|
||||||
// Move everything after the old position down
|
|
||||||
if(direction == 0 && listPosition[itemKey] > fromPosition){
|
|
||||||
listPosition[itemKey]--;
|
|
||||||
}
|
|
||||||
// Move everything from the new position up
|
|
||||||
if(direction == 1 && listPosition[itemKey] >= fromPosition){
|
|
||||||
listPosition[itemKey]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
<h3>Attacking</h3>
|
|
||||||
<p>Some of the changes to the attacking code, that you may remember was shown in it's prior hacked-in form.</p>
|
|
||||||
<pre class="pre--small"><code>startAttack(itemAttacking){
|
|
||||||
if(this.isTapped(itemAttacking)){ return false; }
|
|
||||||
|
|
||||||
this.setEvent('attack', itemAttacking);
|
|
||||||
this.setCardStatus(itemAttacking, 'attacking');
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
<pre class="pre--small"><code>makeAttack(itemDefending, itemAttacking = null){
|
|
||||||
|
|
||||||
// If itemAttacking not defined, use the item from inEvent
|
|
||||||
if(itemAttacking == null){ itemAttacking = inEvent[1]; }
|
|
||||||
|
|
||||||
if(this.isTapped(itemAttacking)){ return false; }
|
|
||||||
|
|
||||||
switch (boardElement[itemDefending]) {
|
|
||||||
|
|
||||||
case 'board':
|
|
||||||
let atkAttacker = this.attackOf(itemAttacking);
|
|
||||||
let atkDefender = this.attackOf(itemDefending);
|
|
||||||
|
|
||||||
// Does Attacker kill Defender
|
|
||||||
if(atkDefender <= atkAttacker){ this.sendToGrave(itemDefending); }
|
|
||||||
|
|
||||||
// Does Defender kill Attacker
|
|
||||||
if(atkAttacker <= atkDefender){
|
|
||||||
this.sendToGrave(itemAttacking);
|
|
||||||
this.endAttackFor(itemAttacking);
|
|
||||||
}
|
|
||||||
else{ this.endAttackFor(itemAttacking); }
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'shield':
|
|
||||||
// If the shield is tapped 'destroy' it
|
|
||||||
if(this.isTapped(itemDefending)){ this.destroyShield(itemDefending); }
|
|
||||||
|
|
||||||
// Otherwise tap the shield, so it can be destroyed in future
|
|
||||||
else{ this.tapCard(itemDefending); }
|
|
||||||
|
|
||||||
this.endAttackFor(itemAttacking);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
<p>And here's the first pass of the eventListener change to start/make attacks.</p>
|
|
||||||
<pre class="pre--small"><code>for(let itemKey = 0; itemKey < item.length; itemKey++){
|
|
||||||
|
|
||||||
if(itemKey in size && itemKey in position && clickableCheck(x,y,itemKey)){
|
|
||||||
|
|
||||||
let playerId = player[itemKey];
|
|
||||||
|
|
||||||
// Check the location of element
|
|
||||||
switch(boardElement[itemKey]){
|
|
||||||
|
|
||||||
case 'board':
|
|
||||||
// playerBoard
|
|
||||||
if(!inEvent && !board.isTapped(itemKey) && playerId == yourPlayerId){
|
|
||||||
board.startAttack(itemKey);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// opponentBoard
|
|
||||||
if(inEvent && inEvent[0] == 'attack' && inEvent[1] != itemKey && playerId != yourPlayerId){
|
|
||||||
// Make attack on the card clicked, with card in inEvent[1]
|
|
||||||
board.makeAttack(itemKey);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
board.drawBoard();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
<h3>Helper Functions</h3>
|
|
||||||
<p>These are smaller functions that perform specific tasks, but written so that I can call them and not replicate code. The examples are simple ones, and some are more simplified for the demonstation.</p>
|
|
||||||
<pre class="pre--small"><code>tapCard(itemKey){ cardStatus[itemKey] = 'tapped'; } </code></pre>
|
|
||||||
<pre class="pre--small"><code>remainingShieldCount(playerId){ return getCurrentPositionAndLength('shield', playerId)[1]; }</code></pre>
|
|
||||||
<pre class="pre--small"><code>isTapped(itemKey){ if(cardStatus[itemKey] == 'tapped'){ return true; } return false; }</code></pre>
|
|
||||||
<pre class="pre--small"><code>isAttacking(itemKey){ if(cardStatus[itemKey] == 'attacking'){ return true; } return false; }</code></pre>
|
|
||||||
<pre class="pre--small"><code>destroyShield(itemKey){
|
|
||||||
|
|
||||||
if(!this.isTapped(itemKey)){ return false; }
|
|
||||||
|
|
||||||
let items = this.getItems('shield', player[itemKey], null, null);
|
|
||||||
for(let item = 0; item < items.length; item++){
|
|
||||||
|
|
||||||
let itemKey = items[item];
|
|
||||||
|
|
||||||
// If ANY of their shields are untapped, you can't destroy target
|
|
||||||
if(!board.isTapped(itemKey)){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shield is now destroyed, move it from shield to owners hand (for the catchup mechanic)
|
|
||||||
this.addShieldToHand(player[itemKey], listPosition[itemKey]);
|
|
||||||
return true;
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
<pre class="pre--small"><code>addShieldToHand(playerFrom = null, listPositionFrom = null){
|
|
||||||
this.addFromBoardElement(playerFrom, listPositionFrom, 'shield', 'hand', null, null);
|
|
||||||
}</code></pre>
|
|
||||||
<pre class="pre--small"><code>sendToGrave(itemKey){
|
|
||||||
this.addFromBoardElement(player[itemKey], listPosition[itemKey], boardElement[itemKey], 'grave', null, null);
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
<p>Some helpers like 'addShieldToHand' have other bits to them too that would trigger other functions, or effects too. Some are literally just one liners to make the code easier to understand and work with.</p>
|
|
||||||
|
|
||||||
<h2>No more hiding what the game is?</h2>
|
|
||||||
<p>For my code examples from now on, they will be near identical to the code at the time I merge the feature the topic is based on into my devlopment branch. Eventually (rather soon in episodic article terms) I will need to detail what the game is to convey the why, so look forwards to that.</p>
|
|
||||||
<p>Being said, up to then I won't directly address the genre, but won't be changing names of 'card', etc. anymore.</p>
|
|
||||||
<p>I hope to keep showing the progressing code-base, and would love for people to follow my development journey, and to inspire others to start a game. If you do start a game, feel free to reference my work in these articles, but try not to rip off everything (despite what I post being mostly unfinished/finalised).</p>
|
|
||||||
|
|
||||||
<h2>Updates</h2>
|
|
||||||
<p>29/10/24 - If you're reading this as a semi-guide, this is not the optimal way to deal with entities/components. I will be rewriting this once the core functionality of the game is written.</p>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,163 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="green">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>Project 1: Front End Debugging</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1 style="font-size: 2.4rem">Project 1: Front End Debugging</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
|
|
||||||
<p>Whilst working on the codebase I found that I had to repeatedly go back into the code, and add console logs just to verify basic things like if the correct card had been moved to the correct listPosition and boardElement.</p>
|
|
||||||
|
|
||||||
<h2>Solution</h2>
|
|
||||||
<p>To prevent the need to keep editing the code for basic logs, I opted to write some front-end debuging tools using the existing <a href="/blog/p1-ecsey-code-restructure.html">ECSey functions</a> I wrote prior.</p>
|
|
||||||
<p>I gave a quick thought of what I regularly need to check, and add some buttons with related function calls dedicated to console logging the results. getItemsAndPrintFrontEnd() is the start of the debugging process.</p>
|
|
||||||
|
|
||||||
<h3>The UI</h3>
|
|
||||||
<p>I've removed the onClick events from the buttons, but the only one you need to know is the second row's button calls getItemsAndPrintFrontEnd(). Those in the first row are remnants of the most basic debugging that I soon realised I needed/wanted better. Well besides 'Untap all' that well, guess what that does.</p>
|
|
||||||
<hr>
|
|
||||||
<button onclick="alert('Never')">Untap all</button>
|
|
||||||
<button onclick="alert('gonna')">Print cardlist to console</button>
|
|
||||||
<button onclick="alert('give')" >Print cardsInDeck for player0</button>
|
|
||||||
<button onclick="alert('you')">Print cardsInDeck for player1</button>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<div>
|
|
||||||
<button onclick="alert('up')">Echo cards: </button>
|
|
||||||
|
|
||||||
<select name="boardElementId" id="boardElementId">
|
|
||||||
<option value="">All</option>
|
|
||||||
<option value="realDeck">realDeck</option>
|
|
||||||
<option value="deck">deck</option>
|
|
||||||
<option value="hand">hand</option>
|
|
||||||
<option value="mana">mana</option>
|
|
||||||
<option value="shield">shield</option>
|
|
||||||
<option value="board">board</option>
|
|
||||||
<option value="grave">grave</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select name="playerId" id="playerId">
|
|
||||||
<option value="">All</option>
|
|
||||||
<option value="0">Player0</option>
|
|
||||||
<option value="1">Player1/Opponent</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<input name="listPositionId" id="listPositionId" placeholder="In position 1,2,3..."></input>
|
|
||||||
|
|
||||||
<select name="cardStatusId" id="cardStatusId">
|
|
||||||
<option value="">All</option>
|
|
||||||
<option value="attacking">Attacking</option>
|
|
||||||
<option value="tapped">Tapped</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select name="itemOrCardData" id="itemOrCardData">
|
|
||||||
<option value="item">Item Data</option>
|
|
||||||
<option value="card">Card Data</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Code examples</h3>
|
|
||||||
<p>The UI (on the second row) then triggers the code below, and console logs the data requested based on the component/attributes set in the front-end UI (above).</p>
|
|
||||||
<pre class="pre--small">function getItemsAndPrintFrontEnd(){
|
|
||||||
|
|
||||||
let boardElementId = document.getElementById("boardElementId").value;
|
|
||||||
if(boardElementId == ""){ boardElementId = null; }
|
|
||||||
|
|
||||||
let playerId = document.getElementById("playerId").value;
|
|
||||||
if(playerId == ""){ playerId = null; }
|
|
||||||
|
|
||||||
let cardStatusId = document.getElementById("cardStatusId").value;
|
|
||||||
if(cardStatusId == ""){ cardStatusId = null; }
|
|
||||||
|
|
||||||
let listPositionId = document.getElementById("listPositionId").value;
|
|
||||||
if(listPositionId == ""){ listPositionId = null; }
|
|
||||||
|
|
||||||
let itemOrCard = document.getElementById("itemOrCardData").value;
|
|
||||||
|
|
||||||
getItemsAndPrint(boardElementId, playerId, cardStatusId, listPositionId, itemOrCard);
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
<p>getItemsAndPrint() is called with the item/entity data from the front-end.</p>
|
|
||||||
<pre class="pre--small">function getItemsAndPrint(boardElementId = null, playerId = null, cardStatusId = null, listPositionId = null, itemOrCard = 'item'){
|
|
||||||
|
|
||||||
let items = board.getItems(boardElementId, playerId, cardStatusId, listPositionId);
|
|
||||||
|
|
||||||
if(itemOrCard == 'card'){
|
|
||||||
console.log('----- CardData -----');
|
|
||||||
printCardData(items);
|
|
||||||
}else{
|
|
||||||
console.log('----- ItemData -----');
|
|
||||||
printECSData(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Items array length: '+items.length);
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
<p>The above then calls one of printCardData() or printECSData() depending on what I need.</p>
|
|
||||||
<p>ECS data returns the data stored in each of the attributes/components for each of the items requested.</p>
|
|
||||||
<pre class="pre--small"><code>function printECSData(items){
|
|
||||||
for(let item = 0; item < items.length; item++){
|
|
||||||
let itemKey = items[item];
|
|
||||||
console.log(
|
|
||||||
'itemId: '+itemKey+"\n"+
|
|
||||||
'boardElement: '+boardElement[itemKey]+"\n"+
|
|
||||||
'cardData: '+cardData[itemKey]+"\n"+
|
|
||||||
'position: '+position[itemKey]+"\n"+
|
|
||||||
'size: '+size[itemKey]+"\n"+
|
|
||||||
'cardStatus: '+cardStatus[itemKey]+"\n"+
|
|
||||||
'player: '+player[itemKey]+"\n"+
|
|
||||||
'listPosition: '+listPosition[itemKey]+"\n"+
|
|
||||||
'cardFace: '+cardFace[itemKey]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
<p>And Card data returns an easier to read array of the cardData for each item requested. This will be the 'base' cardData, as all the elements within the card will eventually be changed into components/attributes so they can be easily altered without changing anything directly in the cardData.</p>
|
|
||||||
<pre class="pre--small"><code>function printCardData(items){
|
|
||||||
let cardArray = [];
|
|
||||||
for(let item = 0; item < items.length; item++){
|
|
||||||
let itemKey = items[item];
|
|
||||||
cardArray.push(cardData[itemKey]);
|
|
||||||
}
|
|
||||||
console.log(cardArray);
|
|
||||||
}</code></pre>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h2>Future Debugging</h2>
|
|
||||||
<p>When I decide it's needed I will be adding more debugging functions, that will be used to trigger events, switch turns, play cards, view opponents hand/deck, etc. All of these will be to make development and testing easier, but for now I'd rather spend the time fleshing out the game itself.</p>
|
|
||||||
<p>There will be DB debug tools, and a toggleable option for me to return logs from each server call in the future, when the code finally gets migrated from front-end to back-end so that I can continue to debug as-if I was working front-end, just with a little more overhead.</p>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="green">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>Project 1: Spreadsheet GDD</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1 style="font-size: 2.4rem">Project 1: Spreadsheet GDD</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<p>Since starting development of this project I had vague ideas of what the mechanics would be, the overarching theme, and the background for it. These things weren't however completely thought through, so I opted to start some kind of GDD.</p>
|
|
||||||
|
|
||||||
<h2>What is a GDD?</h2>
|
|
||||||
<p>It can vary, but a GDD (Game Design Document) is a 'living' document that contains all the mechanics, designs, plans for UI/UX, the story, characters, etc. Essentially, if it's going to be in the game, it's in the GDD, along with any research regarding the rational.</p>
|
|
||||||
<h3>Living?</h3>
|
|
||||||
<p>The 'living' here means that the document changes and gets updated along with the game, if a mechanic is changed in the game, it changes in the GDD. The GDD is typically then shown to any partners, etc. to show them the progress, and let them note any changes they think would be beneficial.</p>
|
|
||||||
<p>A GDD is typically a large word doc, so with this in mind, I didn't use a GDD in the standard sense.</p>
|
|
||||||
|
|
||||||
<h2>Why not use a standard GDD?</h2>
|
|
||||||
<p>I wanted someplace to throw all my ideas, future plans, mechanics, etc. and keep in a uniform location, and to check, and amend each thing with more ease than a standard document, and to be able to pre-plan (and in some instances test) development changes before coding them in.</p>
|
|
||||||
|
|
||||||
<p>This let to creating a google sheet to throw all my basic ideas into, that I can then flesh out organically, and quickly as they evolve.</p>
|
|
||||||
<p>The main difference between a sheet, and doc, is the speed at which I can edit, update, and add notes, along with comparing against my older concepts to decide what should make it into the game, and my development.</p>
|
|
||||||
|
|
||||||
<h2>The Spreadsheet</h2>
|
|
||||||
<p>This game is an exercise in three things I enjoy, the <em>game genre</em> itself (and within that mechanics, and balancing), development, as it's intended as a PC game first which I'm doing with 'realtime' server side <em>development</em> that's new to me. The third thing is <em>world building</em> and writing lore, so that the world of my game feels real and alive.</p>
|
|
||||||
<p>The spreadsheet itself currently consists of numerous tabs to facilitate for notes for completed, incomplete, and future plans for each of the mechanics, balancing, development notes, and lore/world building with them overlapping when needed.</p>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="green">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>Project 1: The Stack, and Rooms</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1>Project 1: The Stack, and Rooms</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<p>The plan is to make a multiplayer PvP game, with defined turns, and per-player customisable elements.
|
|
||||||
I'm being a little vague, but I'm sure you can figure it out from that.</p>
|
|
||||||
|
|
||||||
<h2>The Stack</h2>
|
|
||||||
<p>First I needed to select a stack I could use for this, with decent documentation to starting off an async multiplayer experience.</p>
|
|
||||||
<p>After looking about I landed on a JS stack consisting of JS, and HTML Canvas for the front-end, and JS, NodeJS, SocketIO, and MySQL for the back-end. This stack should allow me to work on the game itself, rather than look into TCP interfacing (which may be simple, but I have enough stuff on at current to not want to look into this, at least for the time being).</p>
|
|
||||||
|
|
||||||
<h2>Rooms</h2>
|
|
||||||
<p>To get the swing of the multiplayer aspect, I opted to start with rooms.
|
|
||||||
Well that, and the Socket.IO guide I used to start this off with had basic room functionality (add player to room, start the game, etc).</p>
|
|
||||||
|
|
||||||
<h3>What is a Room?</h3>
|
|
||||||
<p>
|
|
||||||
A room/instance (in my project at least), is a means to have multiple game instances running on the same server. Since there will be XvX players during a game, but there can be more than 2X players on the server, the servers resources can be better utilised by running many smaller instances, that manage themselves on the host.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Current Room Functionality</h3>
|
|
||||||
<p>
|
|
||||||
Each room will eventually manage the state of the game being played in said room. For the time, I currently have a room in the most basic form, it has a count of the players in said room, a specific (non-unique) identifier, and name. As I mentioned, very basic, but it sets the field for what will be needed in the future.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Along with the room itself, I have a number of helper functions that are triggered in the client with socket emits, and return data to the client from the server with socket.to, and socket.on.
|
|
||||||
</p>
|
|
||||||
<p>These helper functions include:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Requesting all existing rooms</li>
|
|
||||||
<li>Creating a new room (if there is space to do so)</li>
|
|
||||||
<li>Joining an existing room via ID (again if there is space to do so)</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The first two are near-final I assume, but joining a room currently just uses a numeric counter to log the players already connected, so it allows the same player to join the same, and multiple different rooms at the same time. This will be changed later down the line, but for now there's a decent proof-of-concept to show me that what I want it feasible without too much difficulty.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>Next up is <a href="/blog/p1-basic-drawing-and-interaction.html">Basic Drawing and Interation</a>.</p>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="green">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>Prioritising my project</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1>Prioritising my project</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<p>It's Friday, so according to my last article, I should have something posted for you.</p>
|
|
||||||
|
|
||||||
<h2>Ok, and?</h2>
|
|
||||||
<p>Well, I do have something, this post. This however is more-so, a last second realisation that
|
|
||||||
I should post something.</p>
|
|
||||||
<p>Today, I've been working on a project of mine. It's a secret for now, until I have something to show,
|
|
||||||
otherwise I won't complete it, or have it anywhere near. I know this because I originally started it 1-2 years ago.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Why are you back on it?</h2>
|
|
||||||
<p>It's a project I wanted to work on for a while. I am <em>not</em> however "back on it", I started from scratch, as I couldn't
|
|
||||||
remember where I was, or what was what. Thankfully (kind of), I didn't get far on before.
|
|
||||||
</p>
|
|
||||||
<p>I may keep y'all updated on this, on my blog, or elsewhere on my site once I get somewhere. I was thinking,
|
|
||||||
perhaps I could do updates every week or so, but of something I did a few weeks prior to then.</p>
|
|
||||||
|
|
||||||
<h2>What about other content?</h2>
|
|
||||||
<p>That's why I opted to write this. Just a quick bit to let you know what's occuring, as I may not post articles for a while, or
|
|
||||||
may seem to be 'ignoring' my site again. I kind of will be, but there is reason, and I hope it goes somewhere, or leads to
|
|
||||||
other projects/tasks getting done!
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>Thanks for reading, and understanding. Apologies if this is a terribly written article, I have written in in 5-10 minutes in the midst
|
|
||||||
of working on the project I mentioned.
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="green">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>Return of the Me</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1>Return of the Me</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<p>It's been a little while, but I've returned, at least for a while.</p>
|
|
||||||
|
|
||||||
<h2>Where did you go?</h2>
|
|
||||||
<p>I just got pre-occupied with life, namely work and the general stresses of it all.
|
|
||||||
The website, my projects, and personal development took a large hit, and I sort of just, fell off.
|
|
||||||
I had a bunch of cool things planned, which I'm going to ignore until I can get back into the swing of writing
|
|
||||||
for the site, but once I can get back to it I'll be doing some fancy bits.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>What's the plan?</h2>
|
|
||||||
<p>The focus for now will be to add new content to the site in the form of <a href="/guides">guides</a>, <a href="/recipes">recipes</a>, and <a href="/blog">articles</a>.
|
|
||||||
I'll be trying to get two 'posts' of those varieties out a week, on mondays and fridays (hey look, this is one of them). And I have done this for a little while already, however I'm already starting to struggle with this.
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li><a href="/guides/linux-file-permissions.html">Linux File Permissions</a> Guide</li>
|
|
||||||
<li><a href="/guides/docker-install.html">Docker Installation</a> Guide</li>
|
|
||||||
<li><a href="/guides/linux-acl.html">Linux ACL</a> Guide</li>
|
|
||||||
<li><a href="/recipes/protein-oat-cookies.html">Protein Oat Cookies</a> Recipe</li>
|
|
||||||
</ul>
|
|
||||||
<p>When I get comfortable making the weekly posts, I'll be going back through the 'existing' articles that I've left unfinished, or just there with no content, and finish those up.
|
|
||||||
Then I'll be making improvements to the site such as adding some new content, and also re-focussing on my <a href="/projects.html">projects</a>, especially those not currently listed.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Sellout!</h2>
|
|
||||||
<p>There are now affiliate links around the site to attempt to get a little cash.
|
|
||||||
I'm not expecting a lot, or really anything from them, and I'm not intending to make them obstrusive at all,
|
|
||||||
but if I reference a service, item, etc. I will throw a link to it, and if I can I will use an affiliate code within the link.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>I'm also going to be adding some Javascript too, only a tiny bit to improve the user experience, things such as code snippet copying and basic filter searching on articles, et cetera.
|
|
||||||
|
|
||||||
<h2>Why are you back?</h2>
|
|
||||||
<p>This website is something I wanted to do, and I still do. My lack of energy for it doesn't mean I want to stop working on it.</p>
|
|
||||||
<p>If all goes well, I'd love to prioritise the site, and ideally start my own web-dev pursuit instead of working for someone else, so we'll see.</p>
|
|
||||||
<p>If you're reading this, and visit my site at all, thank you.</p>
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
/* Form stuff */
|
|
||||||
/* If I do a CSS framework, it'll need to have a reset too... */
|
|
||||||
/* e.g. https://github.com/necolas/normalize.css/blob/master/normalize.css
|
|
||||||
https://byby.dev/normalize-css#:~:text=css%23css%2Dresets-,Normalize.,experience%20for%20each%20web%20browser.
|
|
||||||
https://www.joshwcomeau.com/css/custom-css-reset/
|
|
||||||
*/
|
|
||||||
form{
|
|
||||||
border:1px solid;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
input,select,textarea{
|
|
||||||
font:1.2rem/1.62 sans-serif;color:#556;
|
|
||||||
border:1px solid #CCC;
|
|
||||||
}
|
|
||||||
textarea{
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,select,textarea{
|
|
||||||
background: #EEE;
|
|
||||||
border: 2px solid #556;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #556;
|
|
||||||
display: block;
|
|
||||||
line-height: 1;
|
|
||||||
vertical-align: top;
|
|
||||||
transition-duration:.2s;
|
|
||||||
}
|
|
||||||
input,textarea{
|
|
||||||
width:100%;
|
|
||||||
}
|
|
||||||
input[type="checkbox"]{
|
|
||||||
display:inline-block;
|
|
||||||
}
|
|
||||||
input:focus,select:focus,textarea:focus {
|
|
||||||
border-color: #9CF;
|
|
||||||
}
|
|
||||||
@media(min-width:720px){
|
|
||||||
input,select,textarea{
|
|
||||||
display:inline-block;
|
|
||||||
max-width:50%; /* so half it can be, with margins factored in... */
|
|
||||||
max-width:346.8px;
|
|
||||||
}
|
|
||||||
input,textarea{
|
|
||||||
width:auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media(prefers-color-scheme:dark){
|
|
||||||
input,select,textarea{
|
|
||||||
background:#CCC;border-color:#CCC;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="A guide to adding a free SSL certificate to your website(s) using cerbot, and automatically renewing them">
|
|
||||||
<meta name="keywords" content="Blog, articles, guide, certbot, SSL, secure certificate, website">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="black">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>at, for one-off cronjobs</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1>at, for one-off cronjobs</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<section>
|
|
||||||
<p><a href="/guides/backup-with-cron.html#add-a-cronjob">Scheduling a cronjob</a> is great, but sometimes you only want it to run once. You could add, then remove the job, or write a script that executes once, but there is a better way.</p>
|
|
||||||
|
|
||||||
<h2>Install at</h2>
|
|
||||||
<pre><code>sudo apt install at</code></pre>
|
|
||||||
|
|
||||||
<h2>Schedule a script with at</h2>
|
|
||||||
<p>The below examples show two different ways to run a script with at.</p>
|
|
||||||
<pre><code>echo /script/to/run | at 20:30</code></pre>
|
|
||||||
<pre><code>at 20:30 -f ./script/to/run</code></pre>
|
|
||||||
<p>For this, the script needs to exist, and needs executable permissions for your user, <code>sudo chmod +x /script/to/run</code>, if you need a lil' hint.</p>
|
|
||||||
|
|
||||||
<p>The time/date passed <code>20:30</code> in this example, can then be passed in a few assorted ways.</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><code>at now +5 minutes</code></li>
|
|
||||||
<li><code>at 11:00 today</code></li>
|
|
||||||
<li><code>at 11:00 tomorrow</code></li>
|
|
||||||
<li><code>at 20:30</code></li>
|
|
||||||
<li><code>at 20:30 093024</code></li>
|
|
||||||
<li><code>at 08:30PM Sep 30</code></li>
|
|
||||||
<li><code>at 08:30PM Sep 30 2024</code></li>
|
|
||||||
<li><code>at 08:30PM Sep 30 24</code></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>Batch a script</h3>
|
|
||||||
<p>Similar to the regular means of scheduling, except the batch will wait for the system load drops to a set point.</p>
|
|
||||||
<pre><code>echo /script/to/run | at -b 20:30</code></pre>
|
|
||||||
<p>You can also run <code>batch</code>, as follows.</p>
|
|
||||||
<pre><code>echo /script/to/run | batch 20:30</code></pre>
|
|
||||||
|
|
||||||
<h2>Check Queue</h2>
|
|
||||||
<pre><code>atq</code></pre>
|
|
||||||
<p>Running the command above, will show you something akin to...</p>
|
|
||||||
<pre><code>10 Sun Sep 29 14:14:00 2024 a user</code></pre>
|
|
||||||
<p>Which broken down shows the ID <code>10</code>, the date/time the item will run <code>Sun Sep 29 14:14:00 2024</code>,
|
|
||||||
the queue the item is in <code>a</code>, and the user executing the item <code>user</code></p>
|
|
||||||
|
|
||||||
|
|
||||||
<h2>Remove from Queue</h2>
|
|
||||||
<p>You can remove an item from the queue, with the <code>atrm</code> command, passing just the ID.</p>
|
|
||||||
<pre><code>atrm 3</code></pre>
|
|
||||||
|
|
||||||
<h2>Mail the user</h2>
|
|
||||||
<p>Like cron, at also allows the ability to mail the user about the task once executed. By default, if the command produces an output, at will mail the user, you can override this with:</p>
|
|
||||||
<pre><code>at -M</code></pre>
|
|
||||||
<p>This will prevent mail being sent to the user. You can also force a mail sent even if there is no output with:</p>
|
|
||||||
<pre><code>at -m</code></pre>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,119 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="A guide to adding a free SSL certificate to your website(s) using cerbot, and automatically renewing them">
|
|
||||||
<meta name="keywords" content="Blog, articles, guide, certbot, SSL, secure certificate, website">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="black">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>Docker Install (inc. Portainer)</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1>Docker Install (inc. Portainer)</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<section>
|
|
||||||
<p>Docker's pretty cool. Containers are a simple way to, well containerise applications. They're essentially a virtual machine with less overhead.</p>
|
|
||||||
<p>This guide assumes you have a <a href="/guides/server-install-debian.html" target="_blank" rel="noopener">debian install</a>, either physical or a VM (I recommend a VM).</p>
|
|
||||||
|
|
||||||
<h2>Installing Docker</h2>
|
|
||||||
<p>The following is the script I use for my docker installations. Copy/Paste or throw into a file and execute it.</p>
|
|
||||||
|
|
||||||
<pre><code>sudo apt-get install ca-certificates curl gnupg
|
|
||||||
|
|
||||||
sudo install -m 0755 -d /etc/apt/keyrings
|
|
||||||
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
|
||||||
|
|
||||||
sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
|
||||||
|
|
||||||
echo \
|
|
||||||
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
|
|
||||||
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
|
|
||||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
|
|
||||||
sudo apt-get update
|
|
||||||
|
|
||||||
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin</code></pre>
|
|
||||||
|
|
||||||
<p>This will download add the required keyring, download the correct docker version for your PCs arhitecture, and the sources, and download the required packages.</p>
|
|
||||||
<p>With this docker is now installed and ready to go!</p>
|
|
||||||
|
|
||||||
<h2>Installing your first container, Portainer</h2>
|
|
||||||
<p>Portainer is a web-GUI for managing docker containers, so I figure it's a good place to start.</p>
|
|
||||||
|
|
||||||
<h3>Docker Run</h3>
|
|
||||||
<p>Not recommended. Use <a href="#compose">compose</a>.</p>
|
|
||||||
<pre><code>docker run -d \
|
|
||||||
-p 9443:9443 \
|
|
||||||
--name portainer \
|
|
||||||
--restart unless-stopped \
|
|
||||||
-v data:/data \
|
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
||||||
portainer/portainer-ce:latest</code></pre>
|
|
||||||
|
|
||||||
<h3 id="compose">Docker Compose</h3>
|
|
||||||
<p>My preferred way of setting up, and running containers. It makes it more managable, and easier to follow.
|
|
||||||
There are a few more 'steps' here, but that's more-so for managing the containers than a requirement.</p>
|
|
||||||
|
|
||||||
<p>First off we'll create a 'docker' directory, with a 'portainer' directory within it in our home directory.</p>
|
|
||||||
<pre><code>mkdir -p ~/docker/portainer && cd ~/docker/portainer && touch docker-compose.yml</code></pre>
|
|
||||||
|
|
||||||
<p>Next from within the portainer directory, open up/create <code>docker-compose.yml</code>.
|
|
||||||
Use your preferred text editor here, I like vim, so I'll be using it in the snippet.</p>
|
|
||||||
|
|
||||||
<p>Paste the following into the file, and save it.</p>
|
|
||||||
<pre><code>services:
|
|
||||||
portainer:
|
|
||||||
image: portainer/portainer-ce:latest
|
|
||||||
container_name: portainer
|
|
||||||
restart: unless-stopped
|
|
||||||
security_opt:
|
|
||||||
- no-new-privileges:true
|
|
||||||
volumes:
|
|
||||||
- /etc/localtime:/etc/localtime:ro
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
- ./data:/data
|
|
||||||
ports:
|
|
||||||
- 9443:9443</code></pre>
|
|
||||||
|
|
||||||
<p>Next we'll run docker compose, which will do as the run command did, pulling the container (if not already done so), and running it.</p>
|
|
||||||
|
|
||||||
<pre><code>sudo docker compose up -d</code></pre>
|
|
||||||
|
|
||||||
<h3>Accessing Portainer</h3>
|
|
||||||
<p>Once this has been run successfully you should be able to access portainer using the IP of the docker host, or localhost (if running docker on the same PC).</p>
|
|
||||||
|
|
||||||
<p><a href="https://localhost:9443">https://localhost:9443</a></p>
|
|
||||||
<p><a href="https://192.168.0.100:9443">https://192.168.0.100:9443</a></p>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="A guide to adding a free SSL certificate to your website(s) using cerbot, and automatically renewing them">
|
|
||||||
<meta name="keywords" content="Blog, articles, guide, certbot, SSL, secure certificate, website">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="black">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>Linux Access Control Lists (ACL)</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1>Linux Access Control Lists (ACL)</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<section>
|
|
||||||
<p>Sometime linux filesystem permissions and ownership can only get you so far, for more complicated, or fine-tuned permissions we'll to use need another method, ACLs.</p>
|
|
||||||
<p>If you don't know much about linux file permissions I recommend you check my first guide on <a href="/guides/linux-file-permissions.html" rel="noopener">Linux File Permissions</a> first, as this guide will only be covering the how-to, and not the why.</p>
|
|
||||||
|
|
||||||
<h2>What are ACLs?</h2>
|
|
||||||
<p>Access Control Lists, ACL for short are essentially a filter that can be set for files and directories to allow/disallow permissions for multiple users and groups without the need to change ownerships.</p>
|
|
||||||
|
|
||||||
<h3>Install ACL</h3>
|
|
||||||
<pre><code>sudo apt install acl</code></pre>
|
|
||||||
|
|
||||||
<h2>Create ACL Entries/Permissions</h2>
|
|
||||||
|
|
||||||
<h3>Directory ACL</h3>
|
|
||||||
<p>Like default linux permissions, the same deal applies for u,g,o/rwx.
|
|
||||||
The main difference being that alternate users/groups can be defined by name such as <code>u:username:rw</code>, and <code>g:groupname:x</code>.</p>
|
|
||||||
<pre><code>setfacl -dm "u:user:rwx" DIRECTORY # New Files
|
|
||||||
setfacl --recursive -m "u:user:rwx" DIRECTORY # Existing Files</code></pre>
|
|
||||||
<p>The above will change the permissions for the directory, and any new children created within it to be rwx for the user "user". The second part of the above will then change all existing child files/directories of the directory to those same ACL permissions.</p>
|
|
||||||
|
|
||||||
<h3>File ACL</h3>
|
|
||||||
<p>Much like directory ACL, except for files. Any standalone files, or those within directories with an ACL set.</p>
|
|
||||||
<pre><code>setfacl -m "u:user:rwx" FILENAME</code></pre>
|
|
||||||
|
|
||||||
|
|
||||||
<h2>View ACL Entries</h2>
|
|
||||||
<p>Sometimes you need to check the permissions, and a <code>ls -l</code> will no longer cut it with ACL in use, so <code>getfacl</code> should be used.</p>
|
|
||||||
<pre><code>getfacl FILENAME</code></pre>
|
|
||||||
<p>The above will show something along the lines of:</p>
|
|
||||||
<pre><code># file: FILENAME
|
|
||||||
# owner: root
|
|
||||||
# group: root
|
|
||||||
user::rwx
|
|
||||||
user:user:rwx
|
|
||||||
group::r-x
|
|
||||||
mask::rwx
|
|
||||||
other::r-x</code></pre>
|
|
||||||
|
|
||||||
<p>The <code>owner</code>, <code>group</code>, <code>user::</code>, <code>group::</code>, and <code>other::</code>, are self-explanitory as they're basic <a href="/guides/linux-file-permissions.html" rel="noopener">Linux File Permission</a> bits 'n' bobs.</p>
|
|
||||||
<p><code>mask::rwx</code> sets the maximum permissions that can be used for the other user/groups that aren't the owners. So having rwx allows different user/groups to be able to have all permissions.</p>
|
|
||||||
<p><code>user:user:rwx</code> shows that the user "user" has rwx permissions for the file.</p>
|
|
||||||
|
|
||||||
<h2>Remove ACL Entries</h2>
|
|
||||||
|
|
||||||
<p>If you're don't want the ACLs anymore, you can always remove them for a file/directory. Adding <code>-R</code> to the command will recursively remove ACL from any children too.</p>
|
|
||||||
<pre><code>setfacl -b FILENAME</code></pre>
|
|
||||||
|
|
||||||
<h3>Remove ACL entry for a specific user/group</h3>
|
|
||||||
<pre><code>setfacl -x "u:user" FILENAME</code></pre>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,116 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="A guide to adding a free SSL certificate to your website(s) using cerbot, and automatically renewing them">
|
|
||||||
<meta name="keywords" content="Blog, articles, guide, certbot, SSL, secure certificate, website">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="black">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>Linux File Permissions</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1>Linux File Permissions</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<section>
|
|
||||||
<p>Having multiple users and groups with access to the same linux PC/server is great, but not everyone should have access to everything, eh.</p>
|
|
||||||
|
|
||||||
<p>For this, we'll use an example file, and switch ownership between root, and our user. So run the following snippet if your're using this as a tutorial, otherwise just read on.</p>
|
|
||||||
<pre><code>mkdir ~/permsExample && cd ~/permsExample && sudo touch FILENAME</code></pre>
|
|
||||||
|
|
||||||
<h2>Basic File Permisssion Breakdown</h2>
|
|
||||||
<p>For certain things, such as computers with only a few users/groups this works splendid.</p>
|
|
||||||
<p>If we run a <code>ls -l FILENAME</code>, we'll see something along the lines of the below.</p>
|
|
||||||
<pre><code>-rw-r--r-- 1 root root 0 Sep 14 13:02 FILENAME</code></pre>
|
|
||||||
<p><code>-rw-r--r--</code> being the permissions for User, Group, and Others which can be rwx for read, write, and executable permissions. Set in groups of 3s for User/Group/Others respectively.</p>
|
|
||||||
<p><code>root root</code> refers to the owner user and owner group.</p>
|
|
||||||
|
|
||||||
|
|
||||||
<h2>Set User/Group Ownership</h2>
|
|
||||||
<p>Each file, and directory within UNIX has an owner user, and owner group.<br>
|
|
||||||
By default when you create a file your user will be the owner, and your usergroup will be the owner group.</p>
|
|
||||||
|
|
||||||
<p>This can be changed with a simple command (that may need to be run with sudo).</p>
|
|
||||||
<pre><code>chown USER FILENAME
|
|
||||||
chown :GROUP FILENAME
|
|
||||||
chown USER:GROUP FILENAME</code></pre>
|
|
||||||
|
|
||||||
<p>The above snippet has the <code>chown</code> command run three different times with different purposes.
|
|
||||||
The first is to change just the owner user, second to change just the owner group,
|
|
||||||
and third to change both at the same time.</p>
|
|
||||||
|
|
||||||
<p>So to change the owner user for FILENAME to our own user, we'd run <code>sudo chown $USER FILENAME</code>, then verify the change by running <code>ls -l FILENAME</code>.</code></p>
|
|
||||||
|
|
||||||
<h2>Set Permissions for User/Group/Others</h2>
|
|
||||||
<p>Now we'll take a look at the file permissions that will affect the owners, and all other users.</p>
|
|
||||||
<p>If we look back at the <code>ls -l FILENAME</code> mentioned earlier you'll recall the brief <code>-rw-r--r--</code> mention.</p>
|
|
||||||
|
|
||||||
<h3>Intro to the rw-r--r-- meaning</h3>
|
|
||||||
<p>There are 10 dashes (-) that can be set, ignoring the first for now leaves us with 9, seperated by 3s.</p>
|
|
||||||
<p>The first group of three <code>rw-</code> in this example shows that the owner user has read/write permissons, but no executable permissions.
|
|
||||||
<br>The second group <code>r--</code>, shows the owner group only has read permissions, not write/executable permissions.
|
|
||||||
<br>And the third group <code>r--</code>, shows that other users have read permissions, but cannot write/execute the files.</p>
|
|
||||||
|
|
||||||
<h3>Set permissions</h3>
|
|
||||||
|
|
||||||
<p>The below commands will set read, write, and execute permissions for the user, and group and give all other users read permissions for filename.file.</p>
|
|
||||||
<pre><code>chmod ug+rwx FILENAME
|
|
||||||
chmod o+r FILENAME</code></pre>
|
|
||||||
|
|
||||||
<p>First thing after the <code>chmod</code> command (and a space) can be any combination of <code>ugoa</code>. Referring to user, group, other, and all.</p>
|
|
||||||
<p>Second, immediately after the letters can be one of <code>+,-,=</code> which are used to add, remove, or set (exact) permissions.</p>
|
|
||||||
<p>Third, any combination of <code>rwx</code> for read, write, executable permissions.</p>
|
|
||||||
<p>Follow this up with a space and the file/directory name and poof, permissions are set. If you'd like to set permissions for all files within a directory (and not just new ones created), also add a <code> -R</code> at the end of the command.</code></p>
|
|
||||||
|
|
||||||
<h4>Set permissions by number</h4>
|
|
||||||
<p>Many guides, examples, snippets, and such do not give their demonstration of setting permissions in the same manner as above, instead they'll use numbers, such as the below.</p>
|
|
||||||
<pre><code>chmod 755 FILENAME</code></pre>
|
|
||||||
|
|
||||||
<p>This chmod command will give rwx permissions to the owner user, and rx to the owner group, and other.</p>
|
|
||||||
|
|
||||||
<p>The numbers are much simpler to understand than you'd think:
|
|
||||||
<br>4 = Read
|
|
||||||
<br>2 = Write
|
|
||||||
<br>1 = Execute/executable
|
|
||||||
</p>
|
|
||||||
<p>These numbers get added up and grant those permissions to the users. So 7 = rwx, 6 = rw, 5 = rx, 3 = wx, 0 = no permissions, etc.</p>
|
|
||||||
<p>You'll also spot that there are 3 numbers, 7,5,5. These are for user, group, and other respectively. So each rwx number up to 7 sets the permissions for different users.</p>
|
|
||||||
|
|
||||||
|
|
||||||
<h3>Chmod directories</h3>
|
|
||||||
<p>The <code>chmod</code> command can also be used on directories. The following example will give all permissions to all users for that directory, and all its child files/directories.</p>
|
|
||||||
<pre><code>chmod a=rwx directoryName -R</code></pre>
|
|
||||||
|
|
||||||
<h2>Extra</h2>
|
|
||||||
<p><a href="/guides/linux-acl.html">Access Control Lists (ACL) Permissions</a> for a guide to access control lists.</p>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
<h1 id="pork-and-apple-burger">Pork and apple burger</h1>
|
|
||||||
<h2 id="time">Time</h2>
|
|
||||||
<p>~35m</p>
|
|
||||||
<h2 id="req">Req</h2>
|
|
||||||
<ul>
|
|
||||||
<li>Oven</li>
|
|
||||||
<li>Baking tray</li>
|
|
||||||
<li>Mixing Bowl</li>
|
|
||||||
<li>Knife</li>
|
|
||||||
</ul>
|
|
||||||
<h2 id="ingredients">Ingredients</h2>
|
|
||||||
<ul>
|
|
||||||
<li>1/2 Apple</li>
|
|
||||||
<li>Breadcrumbs ~10g</li>
|
|
||||||
<li>Pork mince ~250g</li>
|
|
||||||
<li>Cheese ~30g</li>
|
|
||||||
<li>Burger buns</li>
|
|
||||||
</ul>
|
|
||||||
<h3 id="optional">Optional</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Salt, pepper</li>
|
|
||||||
</ul>
|
|
||||||
<h2 id="prep">Prep</h2>
|
|
||||||
<ul>
|
|
||||||
<li>Grate the apple</li>
|
|
||||||
</ul>
|
|
||||||
<h2 id="instructions">Instructions</h2>
|
|
||||||
<ol type="1">
|
|
||||||
<li>Combine the grated apple, breadcrumbs, and a pinch of salt and
|
|
||||||
pepper in a bowl. Add the pork mince, and mix together by hand.</li>
|
|
||||||
<li>Roll the mince into two even balls, then shape into ~1cm thick
|
|
||||||
burgers. Create a dent in the centre of each patty.</li>
|
|
||||||
<li>Add the burgers onto a baking tray and make for ~12-15 mins.</li>
|
|
||||||
<li>(Optional) Once cooked add cheese to the top of the burgers, and add
|
|
||||||
burger buns (halfed and faced up) to the baking tray. Cook for another
|
|
||||||
2-3 mins.</li>
|
|
||||||
</ol>
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="A simple, quick, and delicious torilla recipe.">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="black">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>Protein Oat Cookies</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1>Protein Oat Cookies</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<aside class="page-nav">
|
|
||||||
<ul>
|
|
||||||
<li><a href="#requirements">Requirements</a></li>
|
|
||||||
<li><a href="#ingredients">Ingredients</a></li>
|
|
||||||
<li><a href="#instructions">Instructions</a></li>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
<section>
|
|
||||||
<h2 id="requirements">Requirements</h2>
|
|
||||||
<p>Time: 10-15 mins</p>
|
|
||||||
<ul>
|
|
||||||
<li>Scales</li>
|
|
||||||
<li>Wooden Spoon</li>
|
|
||||||
<li>Mixing Bowl</li>
|
|
||||||
<li>Pan</li>
|
|
||||||
<li>Baking Tray</li>
|
|
||||||
<li>Stovetop</li>
|
|
||||||
<li>Oven</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2 id="ingredients">Ingredients</h2>
|
|
||||||
<p>These are the ingredients for five cookies.</p>
|
|
||||||
<p>Dry ingredients (can be measured together in same bowl)</p>
|
|
||||||
<ul>
|
|
||||||
<li>200g Oats (Whole Preferred)</li>
|
|
||||||
<li>2½1/2 (~80-90g) Scoops Protein Powder</li>
|
|
||||||
<li>~20g/5tsp Chia Seeds</li>
|
|
||||||
<li>~20g/5tsp Ground Almond</li>
|
|
||||||
<li>50g Chocolate (Optional)</li>
|
|
||||||
</ul>
|
|
||||||
<p>Other Ingredients</p>
|
|
||||||
<ul>
|
|
||||||
<li>60g Sultanas</li>
|
|
||||||
<li>100g Honey</li>
|
|
||||||
<li>100g Butter/Lard</li>
|
|
||||||
<li>100ml Warm Water</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2 id="instructions">Instructions</h2>
|
|
||||||
<ol>
|
|
||||||
<li>
|
|
||||||
<h3>Heat up the mixture</h3>
|
|
||||||
<p>Add the Sultanas, Honey, and Butter to a pan.</p>
|
|
||||||
<p>Put the pan on a low-medium heat, and stir occassionally until the butter and honey is liquified.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h3>Combine the dry ingredients</h3>
|
|
||||||
<p>In a bowl add the Oats, Protein Powder, Chia Seeds, and Almond Powder.</p>
|
|
||||||
<p>Mix to combined.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h3>(Optional) Add the Chocolate</h3>
|
|
||||||
<p>Smash some chocolate to oblivion, or use choc. chips.</p>
|
|
||||||
<p>Add the chocolate into the dry ingredients.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h3>Combine all the ingredients</h3>
|
|
||||||
<p>Create a well in the dry ingredients bowl, and pour in the heated mixture.</p>
|
|
||||||
<p>Mix well to combined.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h3>Slam 'em in the oven</h3>
|
|
||||||
<p>Seperate the mixture into 5 equally sized balls/piles on the baking tray.</p>
|
|
||||||
<p>Press each ball down lightly with the back of the spoon.</p>
|
|
||||||
<p>Put in the oven for around 7 minutes, at 180°C</p>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="A simple, quick, and delicious torilla recipe.">
|
|
||||||
<meta name="author" content="Nathan (Aney) Steel">
|
|
||||||
<meta name="theme-color" content="white">
|
|
||||||
<meta name="theme-color" content="black">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/main.css">
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.svg">
|
|
||||||
<title>Tortillas</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<a href="#main" class="vh">Jump directly to main content</a>
|
|
||||||
<h1>Tortillas</h1>
|
|
||||||
<input id="burger-toggle" type="checkbox"/>
|
|
||||||
<label class="burger-container" for="burger-toggle"><div class="burger"></div><span class="sr">Burger menu</span></label>
|
|
||||||
<hr/>
|
|
||||||
<nav>
|
|
||||||
<a href="/">home</a>
|
|
||||||
<a href="/about.html">about</a>
|
|
||||||
<a href="/projects.html">projects</a>
|
|
||||||
<a href="/blog/">blog</a>
|
|
||||||
<a href="/sitemap.html">misc</a>
|
|
||||||
<a href="/support.html">support</a>
|
|
||||||
</nav>
|
|
||||||
<hr/>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="main">
|
|
||||||
<aside class="page-nav">
|
|
||||||
<ul>
|
|
||||||
<li><a href="#requirements">Requirements</a></li>
|
|
||||||
<li><a href="#ingredients">Ingredients</a></li>
|
|
||||||
<li><a href="#instructions">Instructions</a></li>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
<section>
|
|
||||||
<h2 id="requirements">Requirements</h2>
|
|
||||||
<p>Time: ~30 minutes</p>
|
|
||||||
<ul>
|
|
||||||
<li>Scales</li>
|
|
||||||
<li>Wooden Spoon</li>
|
|
||||||
<li>Mixing Bowl</li>
|
|
||||||
<li>Pan</li>
|
|
||||||
<li>Stovetop</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2 id="ingredients">Ingredients</h2>
|
|
||||||
<p>These are the ingredients for four small torillas</p>
|
|
||||||
<ul>
|
|
||||||
<li>100g Flour</li>
|
|
||||||
<li>2g Coarse Salt</li>
|
|
||||||
<li>2g Baking Powder</li>
|
|
||||||
<li>18g Melted Butter, <strong>or</strong> 18g oil/melted lard</li>
|
|
||||||
<li>60ml(/g) Warm Water</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2 id="instructions">Instructions</h2>
|
|
||||||
<ol>
|
|
||||||
<li>
|
|
||||||
<h3>Combine the dry ingredients</h3>
|
|
||||||
<p><em>~ 30seconds-1min</em></p>
|
|
||||||
<p>Add the flour, salt, and baking powder to a bowl.</p>
|
|
||||||
<p>Mix to combined.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h3>Mix in the wets</h3>
|
|
||||||
<p><em>~ 1min</em></p>
|
|
||||||
<p>Melt the butter/lard.</p>
|
|
||||||
<p>Create a well in the centre of the dry mix, and pour in the butter, and warm water.</p>
|
|
||||||
<p>Mix the liquids in the well from the bottom up until it is all combined, and a dough is formed.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h3>Knead, seperate, and rest</h3>
|
|
||||||
<p><em>~ 25min</em></p>
|
|
||||||
<p>Knead the dough, first in the bowl, then on a lightly floured surface.</p>
|
|
||||||
<p>Divide the dough into four equal portions, and coat lightly with flour.</p>
|
|
||||||
<p>Leave the doughballs to rest for 20mins to an hour, in a warm area.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h3>Make them 'tillas</h3>
|
|
||||||
<p><em>~ 5mins</em></p>
|
|
||||||
<p>Bring your pan to a medium heat.</p>
|
|
||||||
<p>Roll each of the balls out into a circle, approx 6-7inches.</p>
|
|
||||||
<p>When the pan is hot, add each tortilla to the pan one at a time.</p>
|
|
||||||
<p>Cook one side for ~45s-1m, flip and cook the other for ~15-30seconds.</p>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<hr/>
|
|
||||||
<p>Written by <a href="https://aney.co.uk" target="_blank" rel="noopener">@aney</a> with <a href="https://danluu.com/web-bloat/" target="_blank" rel="noopener">web bloat</a> in mind | <a href="https://github.com/Aney/website" target="_blank" rel="noopener">Source Code</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue