Clearing backlog.

This commit is contained in:
Jacob Guenther 2021-09-16 22:40:16 -08:00
parent 5ef2a6f9fc
commit 27bc498a96
25 changed files with 1087 additions and 371 deletions

View File

@ -6,13 +6,21 @@
{% block page_specific_head %}{% endblock page_specific_head %}
</head>
<body class="dark-theme" onload="first_load()">
{% include "header.html" %}
<body class="dark-theme monospace-font" onload="first_load()">
{% include "header.html" %}
{% include "accessibility_options.html" %}
{% include "theme_options.html" %}
{% block page_content %}{% endblock page_content%}
<main>
<aside>
{% block page_aside %}{% endblock page_aside %}
</aside>
{% include "footer.html" %}
<div id="page-content">
{% block page_content %}{% endblock page_content %}
</div>
</main>
{% include "footer.html" %}
</body>
</html>

View File

@ -5,38 +5,38 @@
<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>
{% block page_aside %}
<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 %}
{{ macros::post_card(post=post) }}
<li><a href="#post-card-{{ post.title }}">skip to "{{ post.title | safe }}"</a></li>
{% endfor %}
</section>
</ul>
<section tabindex="0">
<h2>Posts From Blogs That I Read</h2>
</section>
</div>
</main>
<!--
<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="https://rhapsode.adrian.geek.nz/2020/10/31/why-auditory.html">Why an Auditory Browser?</a></li>
</ul>
</nav>
</aside>
{% endblock page_aside %}
{% block page_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>
{% endblock page_content %}

View File

@ -1,212 +1,109 @@
{% extends "base.html" %}
{% import "macros.html" as macros %}
{% block page_aside %}
<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>
{% endblock page_aside %}
{% 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>
<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>
<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 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>
<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="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>
<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>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>
<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>
</section>
<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 %}

View File

@ -1,9 +1,16 @@
<?xml version="1.0"?>
<rss version="2.0">
<channel>
<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>
<language>en-us</language>
<category>blog</category>
<managingEditor>chmod777@tilde.club</managingEditor>
<webMaster>chmod777@tilde.club</webMaster>
{% for post in posts %}
<item>
<title>{{ post.title | safe }}</title>

View File

@ -1,7 +1,10 @@
<header>
<h1 id="title">{{ title | safe }}</h1>
<a href="#page-content">skip to page content</a>
<p>Since February 2021</p>
<!--
<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>

View File

@ -1,58 +1,58 @@
{% extends "base.html" %}
{% import "macros.html" as macros %}
{% block page_aside %}
<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>
{% endblock page_aside %}
{% 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>
<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>I just finished working a seasonal job that took up all of my free time so I am excited to get back into my projects and hobbies which 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. You can expect a post about it soon.</p>
<p>A project that I just started is a RISC V assembler. I wanted to get away from comming up with my own spec for a language for a while and figured an assembly language would be one of the simplest to implement.</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. Also my 3D modeling skills could use some work.</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>
<p>I don't want this just to be a void where I put stuff. If you have any questions or want to discuss what I put here send me a message.</p>
</section>
<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: chmod777</li>
<li>gitea: tildegit.org/chmod777</li>
</ul>
</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="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>
<section id="botany" tabindex="0" aria-label="My Botany Plant">
<h2>My Botany Plant</h2>
</section>
{% endblock page_content %}

View File

@ -1,5 +1,5 @@
{% macro post_card(post) %}
<section class="post-card" id="post-card-{{ post.title | safe }}">
<section class="post-card post-card-grid" 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 %}
@ -8,4 +8,17 @@
<p class="post-card-content">{{ post.summary }}</p>
</section>
<br>
{% endmacro post_card %}
{% macro project_card(project) %}
<li class="post-card">
<a class="project-card" href="{{ project.url | safe }}">
<img class="project-img"
src="{{ project.image | safe }}">
<h2 class="project-title">
{{project.title | safe}}
</h2>
<p class="project-description">{{ project.summary | safe }}</p>
</a>
</li>
{% endmacro post_card %}

View File

@ -0,0 +1,56 @@
{% extends "base.html" %}
{% import "macros.html" as macros %}
{% block page_specific_head %}
{% endblock page_specific_head %}
{% block page_aside %}
{% endblock page_aside %}
{% block page_content %}
<h2>Creative Commons Icons</h2>
<p>The icons on this page are licensed under the <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode">Creative Commons Attribution-NonCommercial-ShareAlike 4.0</a> license.</p>
<ul>
<li class="post-card">
<figure>
{% include "svg/paper-list-icon.svg" %}
<figcaption>Two stacked sheets of paper. The top sheet depicts a bulleted list.</figcaption>
</figure>
<a href="/assets/svg/paper-list-icon.svg" download>Download Stacked Sheets</a>
</li>
<li class="post-card">
<figure>
{% include "svg/chat-bubbles-icon.svg" %}
<figcaption>Two overlapping chat bubbles.</figcaption>
</figure>
<a href="/assets/svg/chat-bubbles-icon.svg" download>Download Chat Bubbles</a>
</li>
<li class="post-card">
<figure>
{% include "svg/gears-icon.svg" %}
<figcaption>Two interlocking gears. Hover to see them spin.</figcaption>
</figure>
<a href="/assets/svg/gears-icon.svg" download>Download Gears</a>
</li>
</ul>
<style>
li {
display: flex;
}
li a {
justify-self: center;
margin-top: auto;
margin-bottom: auto;
padding: 10px 10px 10px 10px;
}
figure {
display: inline-block;
background: #444;
}
figcaption {
width: 220px;
}
</style>
{% endblock page_content %}

View File

@ -0,0 +1,17 @@
{% extends "base.html" %}
{% import "macros.html" as macros %}
{% block page_specific_head %}
<link rel="stylesheet" href="/css/project_styles.css">
{% endblock page_specific_head %}
{% block page_aside %}
{% endblock page_aside %}
{% block page_content %}
<ul id="project-list">
{% for project in projects %}
{{ macros::project_card(project=project) }}
{% endfor %}
</ul>
{% endblock page_content %}

1
templates/svg/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.svg

View File

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

View File

@ -1,54 +0,0 @@
<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

@ -1,23 +1,26 @@
{% extends "base.html" %}
{% block page_specific_head %}
<link rel="stylesheet" href="/css/pong_settings.css">
<script defer="true" type="text/javascript" src="/assets/js/library.js"></script>
{% endblock page_specific_head %}
{% block page_aside %}
{% include "tech_demos/demos/svg_pong/about_pong.html" %}
{% endblock page_aside %}
{% block page_content %}
<main id="page-content">
<asside id="about-pong">
{% include "tech_demos/demos/about_pong.html" %}
</asside>
{% include "tech_demos/demos/svg_pong/about_svg_pong.html" %}
<div id="main-content">
{% include "tech_demos/demos/about_svg_pong.html" %}
<p>fps: <span id="fps"></span></p>
<p>fps: <span id="fps"></span></p>
{% include "tech_demos/demos/svg_pong/pong.svg" %}
{% include "tech_demos/demos/pong.svg" %}
{% include "tech_demos/demos/svg_pong/pong_controlls.html" %}
{% 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>
<!--
{% include "tech_demos/demos/svg_pong/pong_settings.html" %}
{% include "tech_demos/demos/svg_pong/pong_game_summary_table.html" %}
{% include "tech_demos/demos/svg_pong/pong_latest_games_table.html" %}
-->
{% endblock page_content %}

View File

@ -0,0 +1,15 @@
<header>
<h2>The Game of Pong</h2>
From <a href="">Wikipedia's Pong article</a>
</header>
<br>
<section>
"Pong is a table tennisthemed arcade sports video game, featuring simple two-dimensional graphics, manufactured by Atari and originally released in 1972. It was one of the earliest arcade video games"
<hr>
"Pong was the first commercially successful video game, and it helped to establish the video game industry along with the Magnavox Odyssey. Soon after its release, several companies began producing games that closely mimicked its gameplay."
<hr>
"The game was remade on numerous home and portable platforms following its release. Pong is part of the permanent collection of the Smithsonian Institution in Washington, D.C., due to its cultural impact."
</section>
<footer>
</footer>

View File

@ -0,0 +1,56 @@
<article>
<header>
<h2>About This Project</h2>
<p>About three years ago(2018) I created a pong game that used Scalable Vector Graphics(SVG) for
the graphics instead of a traditional HTML canvas. This is its story.
</p>
<p>Some resources related to SVGs.</p>
<ul>
<li><a href="https://www.wikipedia.org/wiki/Scalable_Vector_Graphics">Scalable Vector Graphics (Wikipedia Article)</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG">SVG: Scalable Vector Graphics (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 was 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
sites had and wanted them for myself.</p>
<p>The 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[Citation Needed] though I had never played it.
Plus with a game as simple as pong it would be impossible for me not to finish it.
And as it turns out anything is possible.</p>
<h3>Was it a Success?</h3>
<p>Partially. I did create a game that used an SVG instead of an
HTMLCanvas. I learned how to move the paddles using the transform
attribute from JavaScript, as well as move the ball along a path using a
nanimateMotion tag.</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 show(ed/s).</p>
<p>Try to have fun against the restless AI.</p>
<footer>
<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>
</article>

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,9 +1,6 @@
<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">

View File

@ -1,6 +1,17 @@
{% extends "base.html" %}
{% import "macros.html" as macros %}
{% %}
{% block page_specific_head %}
<link rel="stylesheet" href="/css/project_styles.css">
{% endblock page_specific_head %}
{% block page_aside %}
{% endblock page_aside %}
{% block page_content %}
<ul id="project-list">
{% for demo in tech_demos %}
{{ macros::project_card(project=demo) }}
{% endfor %}
</ul>
{% endblock page_content %}

View File

@ -0,0 +1,31 @@
<form>
<fieldset>
<legend aria-describedby="about-theme-options">Theme Options</legend>
<p id="about-theme-options">theme options requires javascript</p>
<script>
const id = "about-theme-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="monospace-font">monospace (default)</option>
<option value="default-font">browser default</option>
<!--
<option value="comic-sans-font">Comic Sans</option>
-->
</select>
<input type="button" value="apply settings" onclick="apply_theme_options()">
</fieldset>
</form>

51
ts/src/base.ts Normal file
View File

@ -0,0 +1,51 @@
let pong;
window.onload = function() {
pong = new Pong(document);
}
function error(id: string, elementName: string): string {
return `Failed to find ${elementName} element with id "${id}"`;
}
function getHTMLInput(id: string): HTMLInputElement {
const element = document.getElementById(id);
if (element instanceof HTMLInputElement)
return element;
throw error(id, 'html button');
}
function getHTMLSpan(id: string): HTMLSpanElement {
const element = document.getElementById(id);
if (element instanceof HTMLSpanElement)
return element;
throw error(id, 'html span');
}
function getSVGRectElement(svgDocument: Document, id: string): SVGRectElement {
const element = svgDocument.getElementById(id);
if (element instanceof SVGRectElement)
return element;
throw error(id, 'svg rect');
}
function getSVGTextElement(svgDocument: Document, id: string): SVGTextElement {
const element = svgDocument.getElementById(id);
if (element instanceof SVGTextElement)
return element;
throw error(id, 'svg text');
}
function getSVGCircleElement(svgDocument: Document, id: string): SVGCircleElement {
const element = svgDocument.getElementById(id);
if (element instanceof SVGCircleElement)
return element;
throw error(id, 'svg circle');
}
function getSVGAnimateMotionElement(svgDocument: Document, id: string): SVGAnimateMotionElement {
const element = svgDocument.getElementById(id);
if (element instanceof SVGAnimateMotionElement)
return element;
throw error(id, 'svg animate motion');
}
function getSVGPathElement(svgDocument: Document, id: string): SVGPathElement {
const element = svgDocument.getElementById(id);
if (element instanceof SVGPathElement)
return element;
throw error(id, 'svg path');
}

581
ts/src/pong.ts Normal file
View File

@ -0,0 +1,581 @@
class Vec2 {
constructor(public x: number, public y: number) {}
static zero(): Vec2 {
return new Vec2(0, 0);
}
static one(): Vec2 {
return new Vec2(1, 1);
}
public clone(): Vec2 {
return new Vec2(this.x, this.y);
}
public add(other: Vec2): Vec2 {
return new Vec2(this.x + other.x, this.y + other.y);
}
public sub(other: Vec2): Vec2 {
return new Vec2(this.x - other.x, this.y - other.y);
}
public mul(s: number): Vec2 {
return new Vec2(this.x * s, this.y * s);
}
public div(s: number): Vec2 {
return new Vec2(this.x / s, this.y / s);
}
public cross(other: Vec2): number {
return this.x * other.y - this.y * other.x;
}
public dot(other: Vec2): number {
return this.x * other.y + this.y * other.y;
}
public magnitude2(): number {
return this.x * this.x + this.y * this.y;
}
public magnitude(): number {
return Math.sqrt(this.magnitude2());
}
public asUnit(): Vec2 {
return this.div(this.magnitude());
}
static numberAsString(n: number): string {
if (n != Math.floor(n)) {
n.toFixed(2);
}
return n.toString();
}
public asString(): string {
return `${Vec2.numberAsString(this.x)},${Vec2.numberAsString(this.y)} `;
}
public asAbsolute(): string {
return `M${this.asString()}`;
}
public asLine(): string {
return `L${this.asString()}`;
}
}
class Line {
constructor(public p1: Vec2, public p2: Vec2) {}
public lineLineIntersect(l2: Line): null | Vec2 {
const p = this.p1;
const r = this.p2.sub(p);
const q = l2.p1;
const s = l2.p2.sub(q);
const r_cross_s = r.cross(s);
const q_minus_p = q.sub(p);
if (r_cross_s === 0) {
return null;
// let q_minus_p_cross_r = q_minus_p.cross(r);
// if (q_minus_p_cross_r === 0)
// return null; // collinear
// else
// return null; // parallel
} else {
const t = q_minus_p.cross(s.div(r_cross_s));
const u = q_minus_p.cross(r.div(r_cross_s));
if (0 <= t && t <= 1 && 0 <= u && u <= 1) {
if (t === 0)
return q.add(s.mul(u));
else
return p.add(r.mul(t));
}
else {
return null; // divergent
}
}
}
}
class PongElements {
public playerPaddle: SVGRectElement;
public aiPaddle: SVGRectElement;
public playerScoreElement: SVGTextElement;
public aiScoreElement: SVGTextElement;
public ball: SVGCircleElement;
public ballAnimation: SVGAnimateMotionElement;
public ballPath: SVGPathElement;
public collisionPath: SVGPathElement;
public resetButton: HTMLInputElement;
public serveButton: HTMLInputElement;
public upButton: HTMLInputElement;
public stopButton: HTMLInputElement;
public downButton: HTMLInputElement;
public fps: HTMLSpanElement;
constructor(svgContent: Document) {
this.playerPaddle = getSVGRectElement(svgContent, 'pong-player-paddle');
this.aiPaddle = getSVGRectElement(svgContent, 'pong-ai-paddle');
this.playerScoreElement = getSVGTextElement(svgContent, 'pong-player-score');
this.aiScoreElement = getSVGTextElement(svgContent, 'pong-ai-score');
this.ball = getSVGCircleElement(svgContent, 'pong-ball');
this.ballAnimation = getSVGAnimateMotionElement(svgContent, 'pong-ball-animation');
this.ballPath = getSVGPathElement(svgContent, 'pong-ball-path');
this.collisionPath = getSVGPathElement(svgContent, 'pong-collision-path');
this.resetButton = getHTMLInput('pong-reset');
this.serveButton = getHTMLInput('pong-serve');
this.upButton = getHTMLInput('pong-paddle-up');
this.stopButton = getHTMLInput('pong-paddle-stop');
this.downButton = getHTMLInput('pong-paddle-down');
this.fps = getHTMLSpan('fps');
const WIDTH_STRING = Pong.PADDLE_WIDTH.toString();
this.aiPaddle.setAttribute('width', WIDTH_STRING);
this.playerPaddle.setAttribute('width', WIDTH_STRING);
const BALL_RADIUS_STRING = Pong.BALL_RADIUS.toString();
this.ball.setAttribute('r', BALL_RADIUS_STRING);
this.resetPaddles();
this.resetBall();
}
public resetBall() {
this.ballPath.setAttribute('d', Pong.ABSOLUTE_CENTER);
this.collisionPath.setAttribute('d', Pong.ABSOLUTE_CENTER);
this.ballAnimation.beginElement();
}
public resetPaddles() {
translateToPosition(this.playerPaddle, Pong.PLAYER_STARTING_POSITION);
translateToPosition(this.aiPaddle, Pong.AI_STARTING_POSITION);
}
public resetScores() {
this.playerScoreElement.innerHTML = '0';
this.aiScoreElement.innerHTML = '0';
}
}
class PongState {
public lastAnimationFrame = 0;
public playerPosition = Pong.PLAYER_STARTING_POSITION.clone();
public aiPosition = Pong.AI_STARTING_POSITION.clone();
public ballSpeed = Pong.BALL_BASE_SPEED;
public ballVelocity = Vec2.zero();
public running = false;
public shouldServe = false;
public playerServe = false;
public time = 0;
public lastTime = 0;
public serveTime = 0;
public dt_ms = 0;
public dt = 0;
public playerScore = 0;
public aiScore = 0;
public scoreDuration = 0;
public collisionDuration = 0;
public collisionPoint = Vec2.zero();
public moveUp = false;
public moveDown = false;
get timeSinceServe(): number { return (this.time - this.serveTime) / 1000; }
public update_dt(time: number) {
this.lastTime = this.time;
this.time = time;
this.dt_ms = this.time - this.lastTime;
this.dt = this.dt_ms / 1000;
}
public resetBall() {
this.ballSpeed = Pong.BALL_BASE_SPEED;
this.ballVelocity = Vec2.zero();
}
public resetPaddles() {
this.playerPosition = Pong.PLAYER_STARTING_POSITION.clone();
this.aiPosition = Pong.AI_STARTING_POSITION.clone();
this.moveUp = false;
this.moveDown = false;
}
}
class Pong {
/*
x w
y +---------------------+
| | |
h | 0 |
| | |
+---------------------+
*/
static readonly WIDTH = 512;
static readonly HEIGHT = 256;
static readonly HALF_WIDTH = Pong.WIDTH / 2;
static readonly HALF_HEIGHT = Pong.HEIGHT / 2;
static readonly CENTER = new Vec2(Pong.HALF_WIDTH, Pong.HALF_HEIGHT);
static readonly ABSOLUTE_CENTER = Pong.CENTER.asAbsolute();
static readonly PADDLE_WIDTH = 4;
static readonly PADDLE_HEIGHT = 28;
static readonly PADDLE_HALF_WIDTH = Pong.PADDLE_WIDTH / 2;
static readonly PADDLE_HALF_HEIGHT = Pong.PADDLE_HEIGHT / 2;
static readonly PADDLE_MAX_POSITION = Pong.HEIGHT - Pong.PADDLE_HEIGHT;
static readonly PADDLE_VELOCITY = 150;
static readonly BALL_RADIUS = 5;
static readonly BALL_SPEED_INCREASE = 10;
static readonly BALL_BASE_SPEED = 200;
static readonly PADDLE_STARTING_POSITION_Y = Pong.CENTER.y - Pong.PADDLE_HALF_HEIGHT;
static readonly PLAYER_STARTING_POSITION = new Vec2(0, Pong.PADDLE_STARTING_POSITION_Y);
static readonly AI_STARTING_POSITION = new Vec2(Pong.WIDTH - Pong.PADDLE_WIDTH, Pong.PADDLE_STARTING_POSITION_Y);
static readonly RAW_CORNERS: Array<Vec2> = [
// top left
new Vec2(0, 0+Pong.BALL_RADIUS),
// top right
new Vec2(Pong.WIDTH, 0+Pong.BALL_RADIUS),
// bottom right
new Vec2(Pong.WIDTH, Pong.HEIGHT-Pong.BALL_RADIUS),
// bottom left
new Vec2(0, Pong.HEIGHT-Pong.BALL_RADIUS)
];
static readonly BOARD_CORNERS: Array<Vec2> = [
// top left
new Vec2(
Pong.RAW_CORNERS[0].x-Pong.BALL_RADIUS,
Pong.RAW_CORNERS[0].y),
// top right
new Vec2(
Pong.RAW_CORNERS[1].x+Pong.BALL_RADIUS,
Pong.RAW_CORNERS[1].y),
// bottom right
new Vec2(
Pong.RAW_CORNERS[2].x+Pong.BALL_RADIUS,
Pong.RAW_CORNERS[2].y),
// bottom left
new Vec2(
Pong.RAW_CORNERS[3].x-Pong.BALL_RADIUS,
Pong.RAW_CORNERS[3].y)
];
static readonly PADDLE_COLLISION_CORNERS: Array<Vec2> = [
// top left
new Vec2(
Pong.RAW_CORNERS[0].x+Pong.PADDLE_WIDTH+Pong.BALL_RADIUS,
Pong.RAW_CORNERS[0].y),
// top right
new Vec2(
(Pong.RAW_CORNERS[1].x-Pong.PADDLE_WIDTH)-Pong.BALL_RADIUS,
Pong.RAW_CORNERS[1].y),
// bottom right
new Vec2(
(Pong.RAW_CORNERS[2].x-Pong.PADDLE_WIDTH)-Pong.BALL_RADIUS,
Pong.RAW_CORNERS[2].y),
// bottom left
new Vec2(
Pong.RAW_CORNERS[3].x+Pong.PADDLE_WIDTH+Pong.BALL_RADIUS,
Pong.RAW_CORNERS[3].y)
];
static readonly BOARD_BOUNDS: Array<[Line, boolean]> = [
// top
[new Line(Pong.BOARD_CORNERS[0], Pong.BOARD_CORNERS[1]), false],
// left
[new Line(Pong.BOARD_CORNERS[0], Pong.BOARD_CORNERS[3]), true],
// bottom
[new Line(Pong.BOARD_CORNERS[3], Pong.BOARD_CORNERS[2]), false],
// right
[new Line(Pong.BOARD_CORNERS[1], Pong.BOARD_CORNERS[2]), true]
];
static readonly PADDLE_COLLISION_BOUNDS: Array<[Line, boolean]> = [
// top
[new Line(Pong.PADDLE_COLLISION_CORNERS[0], Pong.PADDLE_COLLISION_CORNERS[1]), false],
// left
[new Line(Pong.PADDLE_COLLISION_CORNERS[0], Pong.PADDLE_COLLISION_CORNERS[3]), true],
// bottom
[new Line(Pong.PADDLE_COLLISION_CORNERS[3], Pong.PADDLE_COLLISION_CORNERS[2]), false],
// right
[new Line(Pong.PADDLE_COLLISION_CORNERS[1], Pong.PADDLE_COLLISION_CORNERS[2]), true]
];
private elements: PongElements;
private state = new PongState();
constructor(svgContent: Document) {
this.elements = new PongElements(svgContent);
this.elements.resetButton.addEventListener('click', () => { this.reset(); });
this.elements.serveButton.addEventListener('click', () => {
if (!this.state.running) {
this.state.running = true;
this.state.shouldServe = true;
}
});
this.elements.upButton.addEventListener('click', () => {
this.state.moveUp = true;
this.state.moveDown = false;
});
this.elements.downButton.addEventListener('click', () => {
this.state.moveUp = false;
this.state.moveDown = true;
});
this.elements.stopButton.addEventListener('click', () => {
this.state.moveUp = false;
this.state.moveDown = false;
});
document.addEventListener('keydown', (event) => { this.handleKeydown(event); });
document.addEventListener('keyup', (event) => { this.handleKeyup(event); });
this.state.lastAnimationFrame = window.requestAnimationFrame((timestamp) => {
this.update(timestamp);
});
}
private serve() {
this.state.shouldServe = false;
this.state.serveTime = this.state.time;
this.state.ballVelocity.x = genRandom();
this.state.ballVelocity.y = genRandom();
this.state.ballVelocity = this.state.ballVelocity.asUnit().mul(this.state.ballSpeed);
if (this.state.ballVelocity.x > 0) {
this.state.playerServe = true;
}
this.buildBallPaths(Pong.CENTER);
}
public reset() {
this.state = new PongState();
window.cancelAnimationFrame(this.state.lastAnimationFrame);
this.updatePaddles();
this.elements.resetScores();
this.elements.resetBall();
}
private resetBall() {
this.state.resetBall();
this.elements.resetBall();
}
private resetPaddles() {
this.state.resetPaddles();
this.updatePaddles();
}
private updatePaddles() {
translateToPosition(this.elements.playerPaddle, this.state.playerPosition);
translateToPosition(this.elements.aiPaddle, this.state.aiPosition);
}
private update(timestamp: number) {
this.state.update_dt(timestamp);
const fps = 1 / this.state.dt;
this.elements.fps.innerHTML = fps.toString();
if (this.state.shouldServe) {
this.serve();
}
this.updatePlayer();
if (this.state.running) {
this.updateAI(this.elements.aiPaddle, this.state.aiPosition);
this.processScore();
if (this.state.running) {
this.processHit();
}
}
this.state.lastAnimationFrame = window.requestAnimationFrame((timestamp) => {
this.update(timestamp);
});
}
private isColliding(paddle: Vec2): boolean {
return paddle.y + Pong.PADDLE_HEIGHT > this.state.collisionPoint.y &&
paddle.y < this.state.collisionPoint.y;
}
private processScore() {
if (this.state.timeSinceServe > this.state.scoreDuration) {
if (this.state.playerServe) {
this.state.playerScore++;
this.elements.playerScoreElement.innerHTML = this.state.playerScore.toString();
} else {
this.state.aiScore++;
this.elements.aiScoreElement.innerHTML = this.state.aiScore.toString();
}
this.state.running = false;
this.resetBall();
this.resetPaddles();
}
}
private processHit() {
if (this.state.timeSinceServe > this.state.collisionDuration) {
let position: Vec2 = this.state.playerPosition;
let collisionOffset: number = 0.1;
if (this.state.playerServe) {
position = this.state.aiPosition;
collisionOffset = -collisionOffset;
}
if (this.isColliding(position)) {
this.state.ballVelocity.x = -this.state.ballVelocity.x;
this.state.ballSpeed += Pong.BALL_SPEED_INCREASE;
this.state.serveTime = this.state.time;
this.state.playerServe = !this.state.playerServe;
this.state.collisionPoint.x += collisionOffset;
this.buildBallPaths(this.state.collisionPoint);
}
}
}
private clampPaddlePosition(paddlePosition: Vec2) {
paddlePosition.y = Math.max(Math.min(paddlePosition.y, Pong.PADDLE_MAX_POSITION), 0);
}
private updatePlayer() {
if (this.state.moveUp && this.state.moveDown
|| !(this.state.moveUp || this.state.moveDown))
{
return;
}
const dy = this.state.dt * Pong.PADDLE_VELOCITY;
if (this.state.moveDown) {
this.state.playerPosition.y += dy;
} else {
this.state.playerPosition.y -= dy;
}
this.clampPaddlePosition(this.state.playerPosition);
translateToPosition(this.elements.playerPaddle, this.state.playerPosition);
}
private updateAI(paddle: SVGRectElement, position: Vec2) {
let targetPosition: number;
if (this.state.playerServe) {
targetPosition = this.state.collisionPoint.y;
} else {
targetPosition = Pong.HALF_HEIGHT;
}
targetPosition -= Pong.PADDLE_HALF_HEIGHT;
const targetRange = Pong.PADDLE_HALF_HEIGHT / 2;
const dy = this.state.dt * Pong.PADDLE_VELOCITY;
let shouldMove = false;
if (position.y > targetPosition + targetRange) {
shouldMove = true;
position.y -= dy;
} else if (position.y < targetPosition - targetRange) {
shouldMove = true;
position.y += dy;
}
if (shouldMove) {
this.clampPaddlePosition(position);
translateToPosition(paddle, position);
}
}
private buildBallPath(startingPosition: Vec2, dir: Vec2, bounds: [Line, boolean][]): Vec2[] {
const path = new Line(startingPosition, startingPosition.add(dir));
const points: Array<Vec2> = [];
while (true) {
const maybeIntersect = this.findNextCollision(path, bounds);
if (maybeIntersect !== null) {
const [point, isEnd] = maybeIntersect;
if (isEnd) {
if (point.x > Pong.HALF_WIDTH)
point.x += 0.01;
else
point.x -= 0.01;
} else {
if (point.y > Pong.HALF_HEIGHT)
point.y -= 0.01;
else
point.y += 0.01;
}
points.push(point);
if (isEnd) {
break;
}
dir.y = -dir.y;
path.p1 = point;
path.p2 = point.add(dir);
} else {
console.error("intersect is null")
break;
}
}
return points;
}
private buildBallPaths(startingPosition: Vec2) {
const dir = this.state.ballVelocity.mul(1000);
const animationPoints = this.buildBallPath(startingPosition, dir.clone(), Pong.BOARD_BOUNDS);
const collisionPoints = this.buildBallPath(startingPosition, dir, Pong.PADDLE_COLLISION_BOUNDS);
this.state.collisionPoint = collisionPoints[collisionPoints.length - 1].clone();
this.state.ballVelocity = dir.div(1000);
const animationPath = this.buildSVGPathFromPoints(startingPosition, animationPoints);
this.elements.ballPath.setAttribute('d', animationPath);
this.state.scoreDuration = this.elements.ballPath.getTotalLength() / this.state.ballSpeed;
this.elements.ballAnimation.setAttribute('dur', this.state.scoreDuration.toString());
const collisionPath = this.buildSVGPathFromPoints(startingPosition, collisionPoints);
this.elements.collisionPath.setAttribute('d', collisionPath);
this.state.collisionDuration = this.elements.collisionPath.getTotalLength() / this.state.ballSpeed;
this.elements.ballAnimation.beginElement();
}
private buildSVGPathFromPoints(start: Vec2, points: Array<Vec2>): string {
let directions = start.asAbsolute();
for (const point of points) {
directions = directions.concat(point.asLine());
}
return directions;
}
private findNextCollision(path: Line, bounds: [Line, boolean][]): null | [Vec2, boolean] {
for (const [line, isEnd] of bounds) {
const maybeIntersect = path.lineLineIntersect(line);
if (maybeIntersect !== null) {
return [maybeIntersect, isEnd];
}
}
return null;
}
private handleKeydown(event: KeyboardEvent) {
const key = event.key;
if (key === ',') {
this.state.moveUp = true;
} else if (key === '.') {
this.state.moveDown = true;
} else if (key === 's') {
if (!this.state.running) {
this.state.running = true;
this.state.shouldServe = true;
}
} else if (key === 'n') {
this.reset();
}
}
private handleKeyup(event: KeyboardEvent) {
const key = event.key;
if (key === ',') {
this.state.moveUp = false;
}
else if (key === '.') {
this.state.moveDown = false;
}
}
}
// generate random number in range(-1, 1)
function genRandom() {
return (Math.random() - 0.5) * 2;
}
function translateToPosition(element: SVGElement, position: Vec2) {
translateTo(element, position.x, position.y);
}
function translateTo(element: SVGElement, x: number, y: number) {
element.setAttribute('transform', `translate(${x}, ${y})`);
}

32
ts/tsconfig.json Normal file
View File

@ -0,0 +1,32 @@
{
"compileOnSave": true,
"compilerOptions": {
"target": "es2017",
"module": "system",
"outFile": "./../public/js/library.js",
"sourceMap": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"alwaysStrict": true,
"diagnostics": true,
"listEmittedFiles": true,
"listFiles": false,
"locale": "en",
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"removeComments": true,
"strict": true,
"strictNullChecks": true,
},
"include": [
"*.ts",
"src/*",
"src/**/*",
],
}