clearing backlog.

This commit is contained in:
Jacob Guenther 2021-09-16 22:39:02 -08:00
parent 86c9c904df
commit 5ef2a6f9fc
44 changed files with 1759 additions and 117 deletions

View File

@ -1,37 +0,0 @@
<html>
<head>
<meta charset="utf-8">
<title>chmod777's tilde</title>
<meta name="description" content="">
<meta name="author" content="chmod777">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<header>
<h1>~chmod777</h1>
<nav>
<a href="/~chmod777/">Home</a>
</nav>
</header>
<main>
<h2></h2>
<p></p>
</main>
<footer>
<nav>
<span>~</span>
<a href="#">Top</a>
<span>~</span>
<a href="/~chmod777/">Home</a>
<span>~</span>
</nav>
<br>
<a id="page-source" href="https://tildegit.org/chmod777/my_blog">Page source</a>
</footer>
</body>
</html>

6
.gitignore vendored Normal file → Executable file
View File

@ -1,2 +1,8 @@
drafts/*
botany_builder/*
Cargo.lock
# Added by cargo
/target

11
Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "my_blog"
version = "0.1.0"
edition = "2018"
[dependencies]
tera = "1.12.1"
serde = { version = "1.0.130", features = ["derive"] }
serde_json = "1.0.64"

View File

@ -1,80 +0,0 @@
:root {
--background-color: #111;
--border-color: #222;
--text-color: #fb5;
--link-color: #f70;
--link-highlight-color: #f20;
--header-color: #ccc;
}
body {
background-color: var(--background-color);
color: var(--text-color);
display: block;
}
@media screen and (min-width: 800px) {
body {
width: 800px;
}
}
@media screen and (min-width: 1000px) {
body {
margin: auto;
display: grid;
width: 1000px;
grid-template-columns: 200px auto 200px;
grid-template-areas:
'head head head'
'nav main .'
'foot foot foot';
}
body > header {
grid-area: head;
}
body > nav {
grid-area: nav;
position: sticky;
top: 0;
height: min-content;
}
body > main {
grid-area: main;
}
body > footer {
grid-area: foot;
}
}
h1, h2, h3, h4, h5, h6 {
color: var(--header-color);
}
body > header {
text-align: center;
}
a {
color: var(--link-color);
font-weight: bold;
}
a:hover {
color: var(--link-highlight-color);
}
img {
max-width: 100%;
}
.icon {
background-color: #aaa;
padding: 5px;
width: 64px;
height: 64px;
}
pre {
background-color: var(--border-color);
color: var(--header-color);
max-width: min-content;
padding: 20px;
border-radius: 5px;
}
footer {
margin: 20px 0px 200px 0px;
text-align: center;
}

52
config.json Normal file
View File

@ -0,0 +1,52 @@
{
"posts": [
{
"url": "/blog/posts/casting_an_aluminium_settlers_of_catan_board.html",
"title": "Casting an Aluminium Settlers of Catan Board",
"date_published": "1 September 2021",
"date_modified": null,
"summary": "",
"template_name": "blog/posts/casting_an_aluminium_settlers_of_catan_board.html"
},
{
"url": "/blog/posts/pi_pico_first_impressions.html",
"title": "Pi Pico First Impressions",
"date_published": "1 September 2021",
"date_modified": null,
"summary": "",
"template_name": "blog/posts/pi_pico_first_impressions.html"
},
{
"url": "/blog/posts/designing_my_first_pcb.html",
"title": "Designing my First PCB",
"date_published": "1 September 2021",
"date_modified": null,
"summary": "",
"template_name": "blog/posts/designing_my_first_pcb.html"
}
],
"archived": [
],
"blogs_that_i_read": [
],
"projects": [
{
"url": "/projects/project/creative_commons_icons.html",
"title": "Creative Commons Icons",
"image": "/assets/svg/paper-list-icon.svg",
"summary": "Icon assets licensed under CC BY-NC-SA 4.0",
"template_name": "projects/project/creative_commons_icons.html"
}
],
"tech_demos": [
{
"url": "/tech_demos/demos/svg_pong.html",
"title": "SVG Pong",
"image": "/assets/svg/pong.svg",
"summary": "The game of Pong using SVG graphics and animations.",
"template_name": "tech_demos/demos/svg_pong.html"
}
]
}

View File

Before

Width:  |  Height:  |  Size: 874 KiB

After

Width:  |  Height:  |  Size: 874 KiB

View File

Before

Width:  |  Height:  |  Size: 389 KiB

After

Width:  |  Height:  |  Size: 389 KiB

View File

Before

Width:  |  Height:  |  Size: 940 KiB

After

Width:  |  Height:  |  Size: 940 KiB

View File

Before

Width:  |  Height:  |  Size: 702 KiB

After

Width:  |  Height:  |  Size: 702 KiB

View File

Before

Width:  |  Height:  |  Size: 576 KiB

After

Width:  |  Height:  |  Size: 576 KiB

62
public/assets/js/main.js Normal file
View File

@ -0,0 +1,62 @@
const color_theme_id = "color-theme";
const font_id = "fonts";
function save_accessibility_settings(theme, font) {
document.cookie = "theme=" + theme + "; path=/";
document.cookie = "font=" + font +"; path=/";
}
function get_saved_accessibility_settings() {
let cookies = document.cookie.split("; ");
let theme = null;
let font = null;
for (let cookie of cookies) {
let [key, value] = cookie.split("=");
console.log("'" + key + "' '" + value + "'");
if (key == "theme") {
theme = value;
}
if (key == "font") {
font = value;
}
}
if (theme != null) {
let color_theme = document.getElementById(color_theme_id);
for (option of color_theme.options) {
if (option.value == theme) {
option.selected = true;
break;
}
}
}
if (font != null) {
let fonts = document.getElementById(font_id);
for (option of fonts.options) {
if (option.value == font) {
option.selected = true;
break;
}
}
}
return [theme, font];
}
function load_accessibility_settings(theme, font) {
const body = document.getElementsByTagName("body")[0];
if (theme.length != 0) {
body.setAttribute("class", theme);
}
if (font.length != 0) {
let class_attribute = body.getAttribute("class");
body.setAttribute("class", class_attribute+" "+font);
}
}
function apply_accessibility_options() {
const theme = document.getElementById(color_theme_id).value;
const font = document.getElementById(font_id).value;
save_accessibility_settings(theme, font);
load_accessibility_settings(theme, font);
}
function first_load() {
let [theme, font] = get_saved_accessibility_settings();
load_accessibility_settings(theme, font);
}

View File

Before

Width:  |  Height:  |  Size: 843 B

After

Width:  |  Height:  |  Size: 843 B

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,72 @@
<svg viewBox="0 0 532 276" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Pong</title>
<desc>Two paddles, a ball, vertical dashed line in the center, and two numbers for player scores.</desc>
<defs>
<linearGradient id="pong-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop id="pong-gradiant-begin" offset="0%" />
<stop id="pong-gradiant-end" offset="100%" />
</linearGradient>
</defs>
<rect id="pong-boarder" width="100%" height="100%" />
<svg x="10" y="10" width="512" height="256" >
<rect id="pong-board" width="100%" height="100%" />
<path id="pong-ball-path" d="M256 128" />
<path id="pong-collision-path" d="M256 128" />
<line id="pong-center-line" x1="256" y1="0" x2="256" y2="256" />
<rect id="pong-player-paddle" width="2" height="28" transform="translate(0, 114)"/>
<rect id="pong-ai-paddle" width="2" height="28" transform="translate(510, 114)"/>
<text id="pong-player-score" class="pong-score" x="85.33" y="64">0</text>
<text id="pong-ai-score" class="pong-score" x="426.66" y="64">0</text>
<circle id="pong-ball" r="3">
<animateMotion id="pong-ball-animation" dur="1.0s" repeatCount="indefinite">
<mpath xlink:href="#pong-ball-path"/>
</animateMotion>
</circle>
</svg>
<style>
#pong-gradiant-begin {
stop-color:blue;
stop-opacity:1;
}
#pong-gradiant-end {
stop-color:red;
stop-opacity:1;
}
#pong-boarder {
fill:url(#pong-gradient);
}
#pong-board {
fill:black;
}
#pong-ball-path {
stroke:blue;
}
#pong-collision-path {
stroke:red;
stroke-dasharray:1;
}
#pong-center-line {
stroke:white;
stroke-dasharray:5;
stroke-width:2;
}
#pong-ai-paddle,#pong-player-paddle,.pong-score {
fill:white;
}
.pong-score {
font-family:'Courier New',Courier,monospace;font-size:2em;
}
#pong-ball {
fill:white;
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,16 @@
fieldset {
width: min-content;
margin-left: auto;
margin-right: auto;
}
fieldset > summary {
text-align:center;
}
fieldset > div {
display: grid;
grid-gap: 5px;
grid-template-columns: max-content min-content;
}
fieldset > div > label {
justify-self: right;
}

214
public/css/styles.css Executable file
View File

@ -0,0 +1,214 @@
.default-font {
font-size: 110%;
}
.monospace-font {
font-family: monospace;
font-size: 125%;
}
.dark-theme {
--background-color: #111;
--border-color: #222;
--text-color: #fb5;
--header-color: #ccc;
--link-color: #f70;
--link-hover-color: #f20;
--menu-hover-color: #222;
--post-card-background: #333;
}
.classic-theme {
--background-color: #fff;
--border-color: #222;
--text-color: #000;
--header-color: #000;
--link-color: #00f;
--link-hover-color: #00f;
--menu-hover-color: #bbb;
--post-card-background: #bbb;
}
body {
background-color: var(--background-color);
color: var(--text-color);
display: block;
}
@media screen and (min-width: 800px) {
body {
width: 800px;
}
}
@media screen and (min-width: 1000px) {
body {
margin: auto;
display: grid;
width: 1000px;
grid-template-columns: auto 1000px auto;
grid-template-areas:
'header header header'
'. accessibility .'
'. main .'
'footer footer footer';
}
body > form {
grid-area: accessibility;
width: max-content;
min-width: min-content;
justify-self: center;
}
body > header {
grid-area: header;
}
body > footer {
grid-area: footer;
}
main {
grid-area: main;
margin: auto;
display: grid;
width: 100%;
grid-gap: 5px;
grid-template-columns: auto 1fr;
grid-template-areas: 'main-aside main-content';
}
#main-content {
grid-area: main-content;
min-width: min-content;
width: 100%;
}
main > aside {
grid-area: main-aside;
position: sticky;
top: 0;
height: min-content;
max-width: 380px;
}
}
h1, h2, h3, h4, h5, h6 {
color: var(--header-color);
}
.link-list > li, .link-list > li > a, .link-padding {
padding: 10px 10px 10px 10px;
}
body > header {
text-align: center;
}
body > header > nav > ul > li {
display: inline-block;
}
body > header > nav > ul > li > a {
padding: 20px 20px 20px 20px;
}
body > header > nav > ul > li > a:hover {
background-color: var(--menu-hover-color);
border-radius: 8px;
}
body > footer {
margin: 20px 0px 200px 0px;
text-align: center;
}
body > form {
margin-left: auto;
margin-right: auto;
max-width: 90%;
}
body > form > fieldset {
display: grid;
width: max-content;
max-width: 90%;
grid-template-columns: auto 1fr;
grid-gap: 10px;
grid-template-areas:
'describe describe'
'color-label color-select'
'fonts-label fonts-select'
'apply apply'
}
body > form > fieldset > p {
grid-area: describe;
}
#color-theme-label {
grid-area: color-label;
text-align: left;
}
#color-theme {
grid-area: color-select;
}
#fonts-label {
grid-area: fonts-label;
text-align: left;
}
#fonts {
grid-area: fonts-select;
}
body > form > fieldset > input {
grid-area: apply;
width: max-content;
min-width: min-content;
justify-self: center;
}
main > aside > nav > ul {
padding-left: 15px;
}
.post-card {
display: grid;
padding: 5px 5px 5px 5px;
background-color: var(--post-card-background);
border-radius: 5px;
grid-template-columns: auto;
grid-template-areas:
'post-card-title'
'post-card-published'
'post-card-modified'
'post-card-content';
}
.post-card-title {
grid-area: post-card-title;
}
.post-card-published {
grid-area: post-card-published;
}.post-card-modified {
grid-area: post-card-modified;
}.post-card-content {
grid-area: post-card-content;
}
a {
color: var(--link-color);
font-weight: bold;
}
a:hover {
color: var(--link-hover-color);
}
img {
max-width: 100%;
}
.icon {
background-color: #aaa;
padding: 5px;
width: 64px;
height: 64px;
}
pre {
background-color: var(--border-color);
color: var(--header-color);
max-width: min-content;
padding: 20px;
border-radius: 5px;
}

30
public/feed.rss Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>chmod777's blog</title>
<link>http://tilde.club/~chmod777/blog/blog.html</link>
<description>chmod777's blog where they post about their tech and non tech projects.</description>
<item>
<title>Casting an Aluminium Settlers of Catan Board</title>
<link>http://tilde.club/~chmod777/blog/posts/casting_an_aluminium_settlers_of_catan_board.html</link>
<description></description>
</item>
<item>
<title>Pi Pico First Impressions</title>
<link>http://tilde.club/~chmod777/blog/posts/pi_pico_first_impressions.html</link>
<description></description>
</item>
<item>
<title>Designing my First PCB</title>
<link>http://tilde.club/~chmod777/blog/posts/designing_my_first_pcb.html</link>
<description></description>
</item>
</channel>
</rss>

View File

@ -0,0 +1,313 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>chmod777's SVG Pong</title>
<meta name="description" content="">
<meta name="author" content="chmod777">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta property="og:type" context="website">
<meta property="og:title" context="chmod777's SVG Pong">
<meta property="og:description" context="">
<meta property="og:url" context="http://tilde.club/~chmod777/tech_demos/demos/svg_pong.html">
<meta property="og:image" context="http://tilde.club/~chmod777/favicon.svg">
<meta property="og:local" context="en-US">
<meta property="og:site_name" context="tilde.club">
<meta property="twitter:card" context="summary">
<meta property="twitter:title" context="chmod777's SVG Pong">
<meta property="twitter:description" context="">
<meta property="twitter:url" context="http://tilde.club/~chmod777/tech_demos/demos/svg_pong.html">
<meta property="twitter:image" context="http://tilde.club/~chmod777/favicon.svg">
<link rel="canonical" href="http://tilde.club/~chmod777/tech_demos/demos/svg_pong.html">
<link rel="stylesheet" href="/css/styles.css">
<script src="/assets/js/main.js"></script>
</head>
<body class="dark-theme" onload="first_load()">
<header>
<h1 id="title">chmod777's SVG Pong</h1>
<a href="#page-content">skip to page content</a>
<p>There is a version of this site available as a <a href="gemini://tilde.club/~chmod777">gemini capsule</a>. It may or may not be up to date.</p>
<nav>
<ul class="link-list">
<li><a href="/">home</a></li>
<li><a href="/blog/blog.html">blog</a></li>
<li><a href="/projects/projects.html">projects</a></li>
<li><a href="/tech_demos/tech_demos.html">tech demos</a></li>
</ul>
</nav>
</header>
<form>
<fieldset>
<legend aria-describedby="about-accessibility-options">Accessibility Options</legend>
<p id="about-accessibility-options">accessibility options requires javascript</p>
<script>
const id = "about-accessibility-options";
const element = document.getElementById(id);
element.innerHTML = element.innerHTML.concat(" (javascript enabled)");
</script>
<label id="color-theme-label" for="color-theme">color theme</label>
<select id="color-theme" name="color-theme">
<option value="dark-theme">dark (default)</option>
<option value="classic-theme">classic</option>
<!--
<option value="high-contrast-theme">high contrast</option>
-->
</select>
<label id="fonts-label" for="fonts">fonts</label>
<select id="fonts" name="fonts">
<option value="default-font">browser default (default)</option>
<option value="monospace-font">monospace</option>
<!--
<option value="comic-sans-font">Comic Sans</option>
-->
</select>
<input type="button" value="apply settings" onclick="apply_accessibility_options()">
</fieldset>
</form>
<main id="page-content">
<asside id="about-pong">
<header>
<h2>The Game of Pong</h2>
</header>
<section>
</section>
<footer>
</footer>
</asside>
<div id="main-content">
<article>
<header>
<h2>About This Project</h2>
<p>About three years ago(2018) I created this little project. I have
recently brushed away most of the bugs so here is my Pong clone which uses
Scalable Vector Graphics(SVG).</p>
<p>Some resources related to SVGs.</p>
<ul>
<li><a href="https://www.wikipedia.org/svg">Wikipedia article</a></li>
<li><a href="https://mdn/">Mozilla documentation</a></li>
</ul>
</header>
<h3>Just Why</h3>
<p>The main reason for this project was to learn about SVG and SVG
animations. Before starting this I had never used an SVG in my own
websites. My only interactions with them is seeing them used on other
websites and the one time I opened up InkScape. Back in the days when I
cared about fancy websites I was envious of the cool animations some
folks had and wanted them for myself.</p>
<p>The other thing that motivated me to create a game was seeing the 13k game
jam. A jam that only accepts games whos entire contents fit in 13kb
including all their assets. I had only heard about it after it was already
over that year but that wasn't going to stop me from creating a tiny
game.</p>
<p>Why Pong?</p>
<p>Pong is second only to Tetris and I had never really played it. Plus I thought I
could actually finnish the project if it was a clone of a simple game.</p>
<h3>Was it a Success?</h3>
<p>Partially. I did create a game that used an SVG image instead of an
HTMLCanvas. I learned how to manipulate the elements with the transform
attribute from JavaScript, as well as us an animateMotion tag to move the
ball along a path.</p>
<p>As a bonus the game was 9kb when compressed when I had "finnished it".
That included the svg, html, and transpiled typescript.</p>
<p>However the game wasn't finnished when I stopped working on it three years
ago but I feel I achieved my goal. Or rather the pong game took me as far
as it could on my SVG adventure. Or at least that's what I thought back
then...</p>
<h3>The Revitalized SVG Pong</h3>
<p>What you see below is the new and improved SVG Pong, with far fewer bugs,
a cleaner(TM) codebase, and better preformace.</p>
<p>I revisted this project because I thought it would be fun. That really is
the main reason. And guess what. It was fun. I got to track down weird
bugs related to collisions and missing state. The project truely was just
slapped together over a weekend and it showed.</p>
<p>Try to have fun against the restless AI.</p>
<p>If you like SVGs checkout some of my <a href="/projects/project/creative_commons_icons.html">SVG icons</a> I made for a now defunct project.</p>
<footer>
</footer>
</article>
<p>fps: <span id="fps"></span></p>
<svg viewBox="0 0 532 276" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Pong</title>
<desc>Two paddles, a ball, vertical dashed line in the center, and two numbers for player scores.</desc>
<defs>
<linearGradient id="pong-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop id="pong-gradiant-begin" offset="0%" />
<stop id="pong-gradiant-end" offset="100%" />
</linearGradient>
</defs>
<rect id="pong-boarder" width="100%" height="100%" />
<svg x="10" y="10" width="512" height="256" >
<rect id="pong-board" width="100%" height="100%" />
<path id="pong-ball-path" d="M256 128" />
<path id="pong-collision-path" d="M256 128" />
<line id="pong-center-line" x1="256" y1="0" x2="256" y2="256" />
<rect id="pong-player-paddle" width="2" height="28" transform="translate(0, 114)"/>
<rect id="pong-ai-paddle" width="2" height="28" transform="translate(510, 114)"/>
<text id="pong-player-score" class="pong-score" x="85.33" y="64">0</text>
<text id="pong-ai-score" class="pong-score" x="426.66" y="64">0</text>
<circle id="pong-ball" r="3">
<animateMotion id="pong-ball-animation" dur="1.0s" repeatCount="indefinite">
<mpath xlink:href="#pong-ball-path"/>
</animateMotion>
</circle>
</svg>
<style>
#pong-gradiant-begin {
stop-color:blue;
stop-opacity:1;
}
#pong-gradiant-end {
stop-color:red;
stop-opacity:1;
}
#pong-boarder {
fill:url(#pong-gradient);
}
#pong-board {
fill:black;
}
#pong-ball-path {
stroke:blue;
}
#pong-collision-path {
stroke:red;
stroke-dasharray:1;
}
#pong-center-line {
stroke:white;
stroke-dasharray:5;
stroke-width:2;
}
#pong-ai-paddle,#pong-player-paddle,.pong-score {
fill:white;
}
.pong-score {
font-family:'Courier New',Courier,monospace;font-size:2em;
}
#pong-ball {
fill:white;
}
</style>
</svg>
<form>
<fieldset>
<legend>Controlls</legend>
<summary>Each button has a keyboard shortcut.</summary>
<br>
<div>
<label id="pong-reset-label" for="pong-reset">New Game shortcut 'N'</label>
<input id="pong-reset" name="pong-reset" type="button" value="New Game">
<label id="pong-serve-label" for="pong-serve">Serve shortcut 'S'</label>
<input id="pong-serve" name="pong-serve" type="button" value="Serve">
<label id="pong-paddle-up-label" for="pong-paddle-up">Paddle Up shortcut '&#x3c'</label>
<input id="pong-paddle-up" name="pong-paddle-up" type="button" value="Paddle Up">
<label id="pong-paddle-stop-label" for="pong-paddle-stop"></label>
<input id="pong-paddle-stop" name="pong-paddle-stop" type="button" value="Paddle Stop">
<label id="pong-paddle-down-label" for="pong-paddle-down">Paddle Down shortcut '>'</label>
<input id="pong-paddle-down" name="pong-paddle-down" type="button" value="Paddle Down">
</div>
</fieldset>
</form>
<form>
<fieldset>
<legend>Game Settings</legend>
<summary></summary>
<div>
<label for="ai-difficulty">AI Difficulty</label>
<select id="ai-difficulty" name="ai-difficulty">
<option value="easy">easy</option>
<option value="normal">normal</option>
<option value="hard">hard</option>
</select>
<label for="paddle-speed">Paddle Speed</label>
<input type="slider" id="paddle-speed" name="paddle-speed">
<label for="ball-speed">Ball Speed</label>
<input type="slider" id="ball-speed" name="ball-speed">
</div>
<input type="button" value="Apply Settings(starts a new game)">
</fieldset>
</form>
<table>
<summary>Game Summary</summary>
<tr>
<td>Difficulty</td>
<th>Won</th>
<th>Lost</th>
<th>Tied</th>
<th>Incomplete</th>
<th>Longest Volley</th>
</tr>
<tr>
<th>Easy</th>
</tr>
<tr>
<th>Normal</th>
</tr>
<tr>
<th>Hard</th>
</tr>
</table>
<table>
<summary>Latest Games</summary>
<tr>
<th>Difficulty</th>
<th>Won/Lost/Tied/Incomplete</th>
<th>Player Score</th>
<th>AI Score</th>
<th>Longest Volley</th>
</tr>
</table>
</div>
</main>
<footer>
<a class="link-padding" href="#">jump to top</a>
<br>
<br>
<a class="link-padding" id="page-source" target="_blank" href="https://tildegit.org/chmod777/my_blog">page source (tildegit in new tab)</a>
</footer>
</body>
</html>

View File

@ -0,0 +1,90 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>chmod777's tech demos</title>
<meta name="description" content="">
<meta name="author" content="chmod777">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta property="og:type" context="website">
<meta property="og:title" context="chmod777's tech demos">
<meta property="og:description" context="">
<meta property="og:url" context="">
<meta property="og:image" context="http://tilde.club/~chmod777/favicon.svg">
<meta property="og:local" context="en-US">
<meta property="og:site_name" context="tilde.club">
<meta property="twitter:card" context="summary">
<meta property="twitter:title" context="chmod777's tech demos">
<meta property="twitter:description" context="">
<meta property="twitter:url" context="">
<meta property="twitter:image" context="http://tilde.club/~chmod777/favicon.svg">
<link rel="canonical" href="">
<link rel="stylesheet" href="/css/styles.css">
<script src="/assets/js/main.js"></script>
</head>
<body class="dark-theme" onload="first_load()">
<header>
<h1 id="title">chmod777's tech demos</h1>
<a href="#page-content">skip to page content</a>
<p>There is a version of this site available as a <a href="gemini://tilde.club/~chmod777">gemini capsule</a>. It may or may not be up to date.</p>
<nav>
<ul class="link-list">
<li><a href="/">home</a></li>
<li><a href="/blog/blog.html">blog</a></li>
<li><a href="/projects/projects.html">projects</a></li>
<li><a href="/tech_demos/tech_demos.html">tech demos</a></li>
</ul>
</nav>
</header>
<form>
<fieldset>
<legend aria-describedby="about-accessibility-options">Accessibility Options</legend>
<p id="about-accessibility-options">accessibility options requires javascript</p>
<script>
const id = "about-accessibility-options";
const element = document.getElementById(id);
element.innerHTML = element.innerHTML.concat(" (javascript enabled)");
</script>
<label id="color-theme-label" for="color-theme">color theme</label>
<select id="color-theme" name="color-theme">
<option value="dark-theme">dark (default)</option>
<option value="classic-theme">classic</option>
<!--
<option value="high-contrast-theme">high contrast</option>
-->
</select>
<label id="fonts-label" for="fonts">fonts</label>
<select id="fonts" name="fonts">
<option value="default-font">browser default (default)</option>
<option value="monospace-font">monospace</option>
<!--
<option value="comic-sans-font">Comic Sans</option>
-->
</select>
<input type="button" value="apply settings" onclick="apply_accessibility_options()">
</fieldset>
</form>
<footer>
<a class="link-padding" href="#">jump to top</a>
<br>
<br>
<a class="link-padding" id="page-source" target="_blank" href="https://tildegit.org/chmod777/my_blog">page source (tildegit in new tab)</a>
</footer>
</body>
</html>

4
rustfmt.toml Normal file
View File

@ -0,0 +1,4 @@
hard_tabs = true
condense_wildcard_suffixes = true
imports_layout = "Vertical"
max_width = 80

191
src/main.rs Normal file
View File

@ -0,0 +1,191 @@
use std::fs;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
extern crate tera;
use tera::{
Context,
Tera,
};
use serde::{
Deserialize,
Serialize,
};
const BLOG_BASE_TITLE: &'static str = "chmod777's blog: ";
#[derive(Clone, Debug, Serialize, Deserialize)]
struct Post {
full_url: Option<String>,
url: String,
title: String,
summary: String,
date_published: String,
date_modified: Option<String>,
template_name: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct TechDemo {
full_url: Option<String>,
url: String,
title: String,
summary: String,
template_name: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct Config {
posts: Vec<Post>,
tech_demos: Vec<TechDemo>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct MyContext {
title: String,
description: String,
canonical_url: String,
favicon_image: String,
page_image: String,
og_type: String,
og_locale: String,
og_site_name: String,
twitter_card: String,
posts: Vec<Post>,
tech_demos: Vec<TechDemo>,
}
impl Default for MyContext {
fn default() -> Self {
Self {
title: "chmod777's homepage".to_owned(),
description: "A place where chmod777 puts their blog posts, project updates, and tech demos.".to_owned(),
canonical_url: "http://tilde.club/~chmod777".to_owned(),
favicon_image: "http://tilde.club/~chmod777/favicon.svg".to_owned(),
page_image: "http://tilde.club/~chmod777/favicon.svg".to_owned(),
og_type: "website".to_owned(),
og_locale: "en-US".to_owned(),
og_site_name: "tilde.club".to_owned(),
twitter_card: "summary".to_owned(),
posts: Vec::new(),
tech_demos: Vec::new(),
}
}
}
fn write_to_file(path: &str, content: &str) {
let path = Path::new(path);
let display = path.display();
let mut file = match File::create(path) {
Ok(file) => file,
Err(error) => panic!("Couldn't create file {}: {}", &display, &error),
};
file.write_all(content.as_bytes()).unwrap();
}
fn read_file(path: &str) -> String {
fs::read_to_string(path).expect(&format!("Failed to read file: {}", path))
}
fn append_str(base: &str, extend: &str) -> String {
let mut s = base.to_owned();
s.push_str(extend);
s
}
fn main() {
let tera = match Tera::new("templates/**/*") {
Ok(tera) => tera,
Err(error_s) => panic!("Parsing error(s): {}", error_s),
};
let config_json_string = read_file("config.json");
let config: Config = serde_json::from_str(&config_json_string).unwrap();
let mut context = MyContext::default();
let base_url = "http://tilde.club/~chmod777";
for mut post in config.posts.iter().cloned() {
post.full_url = Some(append_str(base_url, &post.url));
context.posts.push(post);
}
for mut demo in config.tech_demos.iter().cloned() {
demo.full_url = Some(append_str(base_url, &demo.url));
context.tech_demos.push(demo)
}
{
let index = tera
.render("index.html", &Context::from_serialize(&context).unwrap())
.unwrap();
write_to_file("public/index.html", &index);
}
{
context.title = "chmod777's blog".to_owned();
let blog_context = Context::from_serialize(&context).unwrap();
let blog = tera
.render(
"blog/blog.html",
&blog_context,
)
.unwrap();
write_to_file("public/blog/blog.html", &blog);
let rss_feed = tera
.render("feed.rss", &blog_context)
.unwrap();
write_to_file("public/feed.rss", &rss_feed);
context.og_type = "article".to_owned();
for post in context.posts.iter() {
context.title = append_str(BLOG_BASE_TITLE, &post.title);
context.canonical_url = post.full_url.clone().unwrap();
context.description = post.summary.clone();
let tera_context = &Context::from_serialize(&context).unwrap();
let rendered_post =
tera.render(&post.template_name, &tera_context).unwrap();
let path = {
let mut s = "public".to_owned();
s.push_str(&post.url);
s
};
write_to_file(&path, &rendered_post);
}
}
{
context.title = "chmod777's tech demos".to_owned();
context.og_type = "website".to_owned();
context.canonical_url = "".to_owned();
context.description = "".to_owned();
let tech_demos_context = Context::from_serialize(&context).unwrap();
let tech_demos = tera
.render(
"tech_demos/tech_demos.html",
&tech_demos_context,
)
.unwrap();
write_to_file("public/tech_demos/tech_demos.html", &tech_demos);
for demo in context.tech_demos.iter() {
context.title = append_str("chmod777's ", &demo.title);
context.canonical_url = demo.full_url.clone().unwrap();
context.description = demo.summary.clone();
let demo_context = &Context::from_serialize(&context).unwrap();
let rendered_demo =
tera.render(&demo.template_name, &demo_context).unwrap();
let path = {
let mut s = "public".to_owned();
s.push_str(&demo.url);
s
};
write_to_file(&path, &rendered_demo);
}
}
}

25
svg_config.json Normal file
View File

@ -0,0 +1,25 @@
{
"svgs": [
{
"template": "chat-bubbles-icon.svg",
"title": "Chat Bubbles Icon",
"description": "Two overlapping chat bubbles."
},
{
"template": "gears-icon.svg",
"title": "Gears Icon",
"description": "Two interlocking gears that spin on mouse hover."
},
{
"template": "paper-list-icon.svg",
"title": "Stacked Papers with Bulleted List Icon",
"description": "Two stacked sheet of paper. A bullet list is depicted on the top sheet."
},
{
"template": "pong.svg",
"title": "Pong (Video Game) Table",
"description": "Two rectangle paddles one on either end; A horizontal dashed line and a circle in the center; Boardered by a gradiant from left to right, from green to red."
}
]
}

View File

@ -0,0 +1,31 @@
<form>
<fieldset>
<legend aria-describedby="about-accessibility-options">Accessibility Options</legend>
<p id="about-accessibility-options">accessibility options requires javascript</p>
<script>
const id = "about-accessibility-options";
const element = document.getElementById(id);
element.innerHTML = element.innerHTML.concat(" (javascript enabled)");
</script>
<label id="color-theme-label" for="color-theme">color theme</label>
<select id="color-theme" name="color-theme">
<option value="dark-theme">dark (default)</option>
<option value="classic-theme">classic</option>
<!--
<option value="high-contrast-theme">high contrast</option>
-->
</select>
<label id="fonts-label" for="fonts">fonts</label>
<select id="fonts" name="fonts">
<option value="default-font">browser default (default)</option>
<option value="monospace-font">monospace</option>
<!--
<option value="comic-sans-font">Comic Sans</option>
-->
</select>
<input type="button" value="apply settings" onclick="apply_accessibility_options()">
</fieldset>
</form>

18
templates/base.html Executable file
View File

@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
{% include "base_head_inner.html" %}
{% block page_specific_head %}{% endblock page_specific_head %}
</head>
<body class="dark-theme" onload="first_load()">
{% include "header.html" %}
{% include "accessibility_options.html" %}
{% block page_content %}{% endblock page_content%}
{% include "footer.html" %}
</body>
</html>

View File

@ -0,0 +1,23 @@
<meta charset="utf-8">
<title>{{ title | safe }}</title>
<meta name="description" content="{{ description | safe }}">
<meta name="author" content="chmod777">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta property="og:type" context="{{ og_type | safe }}">
<meta property="og:title" context="{{ title | safe }}">
<meta property="og:description" context="{{ description | safe }}">
<meta property="og:url" context="{{ canonical_url | safe }}">
<meta property="og:image" context="{{ page_image | safe }}">
<meta property="og:local" context="{{ og_locale | safe }}">
<meta property="og:site_name" context="{{ og_site_name | safe }}">
<meta property="twitter:card" context="{{ twitter_card | safe }}">
<meta property="twitter:title" context="{{ title | safe }}">
<meta property="twitter:description" context="{{ description | safe }}">
<meta property="twitter:url" context="{{ canonical_url | safe }}">
<meta property="twitter:image" context="{{ page_image | safe }}">
<link rel="canonical" href="{{ canonical_url | safe }}">
<link rel="stylesheet" href="/css/styles.css">
<script src="/assets/js/main.js"></script>

42
templates/blog/blog.html Normal file
View File

@ -0,0 +1,42 @@
{% extends "base.html" %}
{% import "macros.html" as macros %}
{% block page_specific_head %}
<link rel="alternate" type="application/rss+xml" href="/feed.rss" title="RSS feed for chmod777's blog" >
{% endblock page_specific_head %}
{% block page_content %}
<main id="page-content">
<aside id="skip-links">
<nav>
<a type="application/rss+xml" href="/feed.rss">RSS feed for this blog</a>
<h3>Posts</h3>
<ul class="link-list">
{% for post in posts %}
<li><a href="#post-card-{{ post.title }}">skip to "{{ post.title | safe }}"</a></li>
{% endfor %}
</ul>
<h3><a href="/blog/archived.html">Archived Posts</a></h3>
<h3>Posts From Blogs That I Read</h3>
<ul class="link-list">
<li><a href=""></a></li>
</ul>
</nav>
</aside>
<div id="main-content">
<section tabindex="0">
<h2>Posts</h2>
{% for post in posts %}
{{ macros::post_card(post=post) }}
{% endfor %}
</section>
<section tabindex="0">
<h2>Posts From Blogs That I Read</h2>
</section>
</div>
</main>
{% endblock page_content %}

View File

@ -0,0 +1,212 @@
{% extends "base.html" %}
{% import "macros.html" as macros %}
{% block page_content %}
<main id="page-content">
<aside id="skip-links">
<nav>
<h3>Skip Links</h3>
<ul class="link-list">
<li><a href="#article-header">skip to "Pi Pico First Impressions"</a></li>
<li><a href="#language-support">skip to "Language Support"</a></li>
<li><a href="#real-applications">skip to "Real Applications"</a></li>
</ul>
</nav>
</aside>
<div id="main-content">
<header id="article-header" aria-label="Pi Pico First Impressions" tabindex="0">
<h2>Pi Pico First Impressions</h2>
<p>Specs? Who need those. I wont go deep into the specs because that would be a waist of our time. You can look them up <a href="">somewhere else</a>.</p>
<h3>What is this dual core ARM M0+ microprocessor good for?</h3>
<p></p>
<h3>What shouldn't you use it for?</h3>
<p>This isn't a linux computer like the other Raspberry Pis so don't bother trying to host a server on it or use it to emulate your favorite retro device. Though you certaintly can if you <a href="">try</a>.</p>
<p>It doesn't have wireless connectivity built into it like the esp devices so you have to bring your own at which point you might consider an integrated solution.</p>
</header>
<section id="language-support" aria-label="Language Support" tabindex="0">
<h3>Language Support</h3>
<p>The Raspberry Pi foundation supports two languages on the Pi Pico. C and their port of MicroPython to the board.</p>
<p></p>
<table>
<summary>Table summary</summary>
<thead>
<th>Language</th>
<th>Backer</th>
<th>Recommended IDE</th>
<th>Documentation</th>
</thead>
<tr>
<td>C</td>
<td>Raspberry Pi Foundation</td>
<td>Visual Studio Code</td>
<td>
<ul>
<li>PDF</li>
<li>Website</li>
</ul>
</td>
</tr>
<tr>
<td>MicroPython</td>
<td>Raspberry Pi Foundation</td>
<td>Thonny</td>
<td>
<ul>
<li>PDF</li>
<li>MicroPython Website</li>
</ul>
</td>
</tr>
<tr>
<td>CircuitPython</td>
<td>Adafruit</td>
<td>MuPython</td>
<td>
<ul>
<li>Website</li>
</ul>
</td>
</tr>
<tr>
<td>Arduino C</td>
<td>Arduino</td>
<td>Arduino IDE</td>
<td>
<ul>
<li>Website</li>
</ul>
</td>
</tr>
</table>
</section>
<section id="real-applications" aria-label="Real Applications" tabindex="0">
<h3>Real Applications</h3>
<h4>C++ and MicroPython Support</h4>
<p>To test the language support I created the same program in both MicroPython and C++ using the C SDK. In each program I wrote a driver for the <a href="">MPU-6050</a> a sensor with 6 degrees of freedom, and used it to control 2 servos in order to level them.</p>
<figure>
<img src=""
title="Test Program Setup"
alt="A Pi Pico on a bread board connected to an MPU-6050 and two servos via jumper wires.">
<figcaption>The MPU-6050 is connected to one of the Pico's i2c busses while the servos are connected to PWM pins.</figcaption>
</figure>
<h4>Programmable IO (PIO)</h4>
<p>To test the PIO feature of the board I wrote two programs using the C SDK. Each of them poll a <a href="">key matrix</a> similar to what you would find on a USB keyboard. The first program does it by setting GPIO pins using one of the main cores of the microcontroller while the other polls the keys using the programmable IO state machines.</p>
<figure>
<img src=""
title="Keyboard Matrix Wiring Diagram"
alt="A wiring diagram for a 3 by 3 keyboard matrix.">
<figcaption>Each row is connected to each column by a diode and a button in parrallel. The diodes are oriented so that current flows from the rows to the columns.</figcaption>
</figure>
<p>Lets first take a look at the C++ code.</p>
<code>
<pre>
template<uint8_t row_count, uint8_t col_count>
class Keyboard {
public:
// ... snip ...
void poll_buttons() {
uint8_t key_index = 0;
for (uint8_t col = 0, col_pin = _first_col_pin; col < col_count; col++, col_pin++) {
gpio_put(col_pin, 0);
for (uint8_t row = 0; row < row_count; row++) {
const uint8_t row_pin = _first_row_pin + row;
const bool previous_state{_keys_pressed[key_index]};
const bool is_down{gpio_get(row_pin) == 0};
_keys_pressed[key_index] = is_down;
if (is_down != previous_state) {
const auto event_type = static_cast<KeyEventE>(KeyEventE::KEY_UP - static_cast<int>(is_down));
_key_events[_key_event_count] = KeyEvent {event_type, key_index};
_key_event_count++;
}
key_index++;
}
gpio_put(col_pin, 1);
}
}
private:
std::array<bool, row_count * col_count> _keys_pressed{};
std::array<KeyEvent, row_count * col_count> _key_events{};
</pre>
</code>
<p>Before we can look at the PIO code we first need to review how exactly the Pico's PIO works.</p>
<code>
<pre style="tab-size: 2;">
.program keyboard
.define PUBLIC ROW_COUNT 3
.define PUBLIC COL_COUNT 3
.define COL_0_LOW 0b11110
.define COL_1_LOW 0b11101
.define COL_2_LOW 0b11011
; Assume all switches are open.
; Initialize scratch register X(previous state) to be filled with ones.
; Ones indicate open switches.
start:
set X, 0
mov X, ~X
jmp poll
key_changed:
; copy current state(scratch register Y) into previous state(scratch register X)
mov X, Y
; Write the contents of the input shift register(ISR) to the RX FIFO,
; also clears the ISR.
push
.wrap_target
poll:
; clear the input shift register.
; It will be filled with garbage if we didn't push.
mov ISR, NULL
; set column 0 to low all others to high
; what 31 cycles to allow the signal to stabilize
set PINS, COL_0_LOW [31]
; wait another 31 cycles to continue to allow the signal to stabilize
nop [31]
; read column 0 to the input shift register(ISR)
in PINS, ROW_COUNT
set PINS, COL_1_LOW [31]
nop [31]
in PINS, ROW_COUNT
set PINS, COL_2_LOW [31]
nop [31]
in PINS, ROW_COUNT
; copy ISR into scratch register Y
; flip the bits while copying because the signal is active low
mov Y, ~ISR
; compare current state Y to previous state X
; if state changed goto key_changed
jmp X != Y, key_changed
; keys didn't change so poll again
.wrap
</pre>
</code>
</section>
</div>
</main>
{% endblock page_content %}

18
templates/feed.rss Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>chmod777's blog</title>
<link>http://tilde.club/~chmod777/blog/blog.html</link>
<description>chmod777's blog where they post about their tech and non tech projects.</description>
{% for post in posts %}
<item>
<title>{{ post.title | safe }}</title>
<link>{{ post.full_url | safe }}</link>
<description>{{ post.summary | safe }}</description>
{% if post.image %}
<image>{{ post.image | safe }}</image>
{% endif %}
</item>
{% endfor %}
</channel>
</rss>

6
templates/footer.html Normal file
View File

@ -0,0 +1,6 @@
<footer>
<a class="link-padding" href="#">jump to top</a>
<br>
<br>
<a class="link-padding" id="page-source" target="_blank" href="https://tildegit.org/chmod777/my_blog">page source (tildegit in new tab)</a>
</footer>

13
templates/header.html Normal file
View File

@ -0,0 +1,13 @@
<header>
<h1 id="title">{{ title | safe }}</h1>
<a href="#page-content">skip to page content</a>
<p>There is a version of this site available as a <a href="gemini://tilde.club/~chmod777">gemini capsule</a>. It may or may not be up to date.</p>
<nav>
<ul class="link-list">
<li><a href="/">home</a></li>
<li><a href="/blog/blog.html">blog</a></li>
<li><a href="/projects/projects.html">projects</a></li>
<li><a href="/tech_demos/tech_demos.html">tech demos</a></li>
</ul>
</nav>
</header>

58
templates/index.html Normal file
View File

@ -0,0 +1,58 @@
{% extends "base.html" %}
{% import "macros.html" as macros %}
{% block page_content %}
<main id="page-content">
<aside id="skip-links">
<nav>
<h3>skip links</h3>
<ul class="link-list">
<li><a href="#introductions">skip to "Introductions"</a></li>
<li><a href="#contact-info">skip to "Contact Info"</a></li>
<li><a href="#latest-posts">skip to "Latest Posts"</a></li>
<li><a href="#botany">skip to "My Botany Plant"</a></li>
</ul>
</nav>
</aside>
<div id="main-content">
<section id="introductions" aria-label="Introductions">
<h2>Introductions</h2>
<p>Hello!</p>
<p>On the internet I go by chmod777. Feel free to ask me anything.</p>
<p>A computer science graduate with only vague ideas of what they are doing in life.</p>
<p>Lately my life has been revolving around a blue colar job that just ended. In my limited spare time I work on side projects that interest me. They will be the the focus of this blog.</p>
<p>If you follow me on mastodon you may have seen my posts about one of my projects Roll Lang. It is a dice notation interpreter with similar syntax to Roll20's. It's getting close to a 1.0 version.</p>
<p>I'm interested in contributing to a decentralized web. Currently I'm working on an application that uses conflict-free replicated data types(CRDT) for syncronous editing of a digital Kanban style board.</p>
<p>Gardening is a hobby of mine. I'll give an update on the garden when I prune the cherry and apple trees this fall.</p>
<p>I've slowly been building an aluminium Settlers of Cattan board out of casted aluminum tiles. This is my longest running project mostly due to the weather, my casting setup, and motivation to deal with both.</p>
<p>It's been years since I've used an arduino but I recently bought a Raspberry Pi Pico and have been tinkering with it, as well as diving into KiCad. I've been designing a wireless controller for future electronics projects which may include a wheeled robot, and/or a drone.</p>
<p>I've started many Vulkan, OpenGL, and WebGL projects with grand intentions. One of these days I'll get around to getting one of them into a state that I feel is worth sharing.</p>
</section>
<section id="contact-info" aria-label="Contact Info">
<h2>Contact Info</h2>
<ul>
<li>fediverse/mastodon: @chmod777@tilde.zone</li>
<li>irc.tilde.chat: chmodrwx or chmod777</li>
<li>gitea: tildegit.org/chmod777</li>
</ul>
</section>
<section id="latest-posts" aria-label="Latest Posts">
<h2>Latest Posts</h2>
{% for post in posts | slice(end=3) %}
{{ macros::post_card(post=post) }}
{% endfor %}
<ul>
<li><a href="/blog/blog.html">blog home page</a></li>
<li><a href="/blog/archived.html">archived posts</a></li>
</ul>
</section>
<section id="botany" tabindex="0" aria-label="My Botany Plant">
<h2>My Botany Plant</h2>
</section>
</div>
</main>
{% endblock page_content %}

11
templates/macros.html Normal file
View File

@ -0,0 +1,11 @@
{% macro post_card(post) %}
<section class="post-card" id="post-card-{{ post.title | safe }}">
<h3 class="post-card-title"><a href="{{ post.url | safe }}">{{ post.title | safe }}</a></h3>
<span class="post-card-published">date published: <time datetime="{{ post.date_published | safe }}">{{ post.date_published | safe }}</time></span>
{% if post.date_modified %}
<span class="post-card-modified">last modified: <time datetime="{{ post.date_modified | safe }}">{{ post.date_modified | safe }}</time></span>
{% endif %}
<p class="post-card-content">{{ post.summary }}</p>
</section>
<br>
{% endmacro post_card %}

View File

@ -0,0 +1,9 @@
<header>
<h2>The Game of Pong</h2>
</header>
<section>
</section>
<footer>
</footer>

View File

@ -0,0 +1,54 @@
<article>
<header>
<h2>About This Project</h2>
<p>About three years ago(2018) I created this little project. I have
recently brushed away most of the bugs so here is my Pong clone which uses
Scalable Vector Graphics(SVG).</p>
<p>Some resources related to SVGs.</p>
<ul>
<li><a href="https://www.wikipedia.org/svg">Wikipedia article</a></li>
<li><a href="https://mdn/">Mozilla documentation</a></li>
</ul>
</header>
<h3>Just Why</h3>
<p>The main reason for this project was to learn about SVG and SVG
animations. Before starting this I had never used an SVG in my own
websites. My only interactions with them is seeing them used on other
websites and the one time I opened up InkScape. Back in the days when I
cared about fancy websites I was envious of the cool animations some
folks had and wanted them for myself.</p>
<p>The other thing that motivated me to create a game was seeing the 13k game
jam. A jam that only accepts games whos entire contents fit in 13kb
including all their assets. I had only heard about it after it was already
over that year but that wasn't going to stop me from creating a tiny
game.</p>
<p>Why Pong?</p>
<p>Pong is second only to Tetris and I had never really played it. Plus I thought I
could actually finnish the project if it was a clone of a simple game.</p>
<h3>Was it a Success?</h3>
<p>Partially. I did create a game that used an SVG image instead of an
HTMLCanvas. I learned how to manipulate the elements with the transform
attribute from JavaScript, as well as us an animateMotion tag to move the
ball along a path.</p>
<p>As a bonus the game was 9kb when compressed when I had "finnished it".
That included the svg, html, and transpiled typescript.</p>
<p>However the game wasn't finnished when I stopped working on it three years
ago but I feel I achieved my goal. Or rather the pong game took me as far
as it could on my SVG adventure. Or at least that's what I thought back
then...</p>
<h3>The Revitalized SVG Pong</h3>
<p>What you see below is the new and improved SVG Pong, with far fewer bugs,
a cleaner(TM) codebase, and better preformace.</p>
<p>I revisted this project because I thought it would be fun. That really is
the main reason. And guess what. It was fun. I got to track down weird
bugs related to collisions and missing state. The project truely was just
slapped together over a weekend and it showed.</p>
<p>Try to have fun against the restless AI.</p>
<p>If you like SVGs checkout some of my <a href="/projects/project/creative_commons_icons.html">SVG icons</a> I made for a now defunct project.</p>
<footer>
</footer>
</article>

View File

@ -0,0 +1,72 @@
<svg viewBox="0 0 532 276" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Pong</title>
<desc>Two paddles, a ball, vertical dashed line in the center, and two numbers for player scores.</desc>
<defs>
<linearGradient id="pong-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop id="pong-gradiant-begin" offset="0%" />
<stop id="pong-gradiant-end" offset="100%" />
</linearGradient>
</defs>
<rect id="pong-boarder" width="100%" height="100%" />
<svg x="10" y="10" width="512" height="256" >
<rect id="pong-board" width="100%" height="100%" />
<path id="pong-ball-path" d="M256 128" />
<path id="pong-collision-path" d="M256 128" />
<line id="pong-center-line" x1="256" y1="0" x2="256" y2="256" />
<rect id="pong-player-paddle" width="2" height="28" transform="translate(0, 114)"/>
<rect id="pong-ai-paddle" width="2" height="28" transform="translate(510, 114)"/>
<text id="pong-player-score" class="pong-score" x="85.33" y="64">0</text>
<text id="pong-ai-score" class="pong-score" x="426.66" y="64">0</text>
<circle id="pong-ball" r="3">
<animateMotion id="pong-ball-animation" dur="1.0s" repeatCount="indefinite">
<mpath xlink:href="#pong-ball-path"/>
</animateMotion>
</circle>
</svg>
<style>
#pong-gradiant-begin {
stop-color:blue;
stop-opacity:1;
}
#pong-gradiant-end {
stop-color:red;
stop-opacity:1;
}
#pong-boarder {
fill:url(#pong-gradient);
}
#pong-board {
fill:black;
}
#pong-ball-path {
stroke:blue;
}
#pong-collision-path {
stroke:red;
stroke-dasharray:1;
}
#pong-center-line {
stroke:white;
stroke-dasharray:5;
stroke-width:2;
}
#pong-ai-paddle,#pong-player-paddle,.pong-score {
fill:white;
}
.pong-score {
font-family:'Courier New',Courier,monospace;font-size:2em;
}
#pong-ball {
fill:white;
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,24 @@
<form>
<fieldset>
<legend>Controlls</legend>
<summary>Each button has a keyboard shortcut.</summary>
<br>
<div>
<label id="pong-reset-label" for="pong-reset">New Game shortcut 'N'</label>
<input id="pong-reset" name="pong-reset" type="button" value="New Game">
<label id="pong-serve-label" for="pong-serve">Serve shortcut 'S'</label>
<input id="pong-serve" name="pong-serve" type="button" value="Serve">
<label id="pong-paddle-up-label" for="pong-paddle-up">Paddle Up shortcut '&#x3c'</label>
<input id="pong-paddle-up" name="pong-paddle-up" type="button" value="Paddle Up">
<label id="pong-paddle-stop-label" for="pong-paddle-stop"></label>
<input id="pong-paddle-stop" name="pong-paddle-stop" type="button" value="Paddle Stop">
<label id="pong-paddle-down-label" for="pong-paddle-down">Paddle Down shortcut '>'</label>
<input id="pong-paddle-down" name="pong-paddle-down" type="button" value="Paddle Down">
</div>
</fieldset>
</form>

View File

@ -0,0 +1,20 @@
<table>
<summary>Game Summary</summary>
<tr>
<td>Difficulty</td>
<th>Won</th>
<th>Lost</th>
<th>Tied</th>
<th>Incomplete</th>
<th>Longest Volley</th>
</tr>
<tr>
<th>Easy</th>
</tr>
<tr>
<th>Normal</th>
</tr>
<tr>
<th>Hard</th>
</tr>
</table>

View File

@ -0,0 +1,10 @@
<table>
<summary>Latest Games</summary>
<tr>
<th>Difficulty</th>
<th>Won/Lost/Tied/Incomplete</th>
<th>Player Score</th>
<th>AI Score</th>
<th>Longest Volley</th>
</tr>
</table>

View File

@ -0,0 +1,23 @@
<form>
<fieldset>
<legend>Game Settings</legend>
<summary></summary>
<div>
<label for="ai-difficulty">AI Difficulty</label>
<select id="ai-difficulty" name="ai-difficulty">
<option value="easy">easy</option>
<option value="normal">normal</option>
<option value="hard">hard</option>
</select>
<label for="paddle-speed">Paddle Speed</label>
<input type="slider" id="paddle-speed" name="paddle-speed">
<label for="ball-speed">Ball Speed</label>
<input type="slider" id="ball-speed" name="ball-speed">
</div>
<input type="button" value="Apply Settings(starts a new game)">
</fieldset>
</form>

View File

@ -0,0 +1,23 @@
{% extends "base.html" %}
{% block page_content %}
<main id="page-content">
<asside id="about-pong">
{% include "tech_demos/demos/about_pong.html" %}
</asside>
<div id="main-content">
{% include "tech_demos/demos/about_svg_pong.html" %}
<p>fps: <span id="fps"></span></p>
{% include "tech_demos/demos/pong.svg" %}
{% include "tech_demos/demos/pong_controlls.html" %}
{% include "tech_demos/demos/pong_settings.html" %}
{% include "tech_demos/demos/pong_game_summary_table.html" %}
{% include "tech_demos/demos/pong_latest_games_table.html" %}
</div>
</main>
{% endblock page_content %}

View File

@ -0,0 +1,6 @@
{% extends "base.html" %}
{% %}
{% block page_content %}
{% endblock page_content %}