Projects now using article template everywhere
This commit is contained in:
parent
993181ba0d
commit
fa6b030d98
12 changed files with 98 additions and 580 deletions
|
|
@ -9,7 +9,7 @@ use core::panic;
|
|||
use sqlx::PgPool;
|
||||
use std::{collections::HashMap, error::Error};
|
||||
|
||||
use super::{root::AppState, HtmlTemplate, NavBar};
|
||||
use super::{root::AppState, ArticleTemplate, HtmlTemplate, NavBar};
|
||||
|
||||
pub fn get_router() -> Router {
|
||||
Router::new()
|
||||
|
|
@ -37,12 +37,10 @@ struct BlogTemplate {
|
|||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "Article.html")]
|
||||
struct ArticleTemplate {
|
||||
active_navbar: &'static str,
|
||||
next: String,
|
||||
#[template(path = "blog_footer.html")]
|
||||
struct BlogFooterTemplate {
|
||||
previous: String,
|
||||
content: String,
|
||||
next: String,
|
||||
}
|
||||
|
||||
async fn article(
|
||||
|
|
@ -55,9 +53,7 @@ async fn article(
|
|||
Ok(a) => *a,
|
||||
Err(_) => panic!("Not an article at all!!!!"),
|
||||
};
|
||||
let template = ArticleTemplate {
|
||||
active_navbar: NavBar::BLOG,
|
||||
content: article.content,
|
||||
let footer = BlogFooterTemplate {
|
||||
previous: match article.previous {
|
||||
Some(a) => a,
|
||||
None => "".to_string(),
|
||||
|
|
@ -67,6 +63,11 @@ async fn article(
|
|||
None => "".to_string(),
|
||||
},
|
||||
};
|
||||
let template = ArticleTemplate {
|
||||
active_navbar: NavBar::BLOG,
|
||||
content: article.content,
|
||||
footer: footer.to_string(),
|
||||
};
|
||||
HtmlTemplate(template)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,3 +43,11 @@ impl NavBar {
|
|||
pub const ABOUT: &'static str = "about";
|
||||
pub const CONTACT: &'static str = "contact";
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "article.html")]
|
||||
pub struct ArticleTemplate {
|
||||
active_navbar: &'static str,
|
||||
footer: String,
|
||||
content: String,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,27 @@
|
|||
use achubb_database::data::{PsqlData, project::Project};
|
||||
use axum::extract::{Extension, Path};
|
||||
use askama::Template;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::{routing::get, Router};
|
||||
use std::{ error::Error, collections::HashMap};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use super::{HtmlTemplate, NavBar};
|
||||
use super::{root::AppState, ArticleTemplate, HtmlTemplate, NavBar};
|
||||
|
||||
pub fn get_router() -> Router {
|
||||
Router::new()
|
||||
.route("/", get(projects))
|
||||
.route("/archserver", get(archserver))
|
||||
.route("/tak", get(tak))
|
||||
.route("/ed", get(ed))
|
||||
.route("/:project", get(project))
|
||||
}
|
||||
|
||||
pub async fn projects() -> impl IntoResponse {
|
||||
pub async fn projects(state: Extension<AppState>) -> impl IntoResponse {
|
||||
let db_pool = &state.db;
|
||||
let list: Vec<String> = get_projects_as_links_list(db_pool)
|
||||
.await
|
||||
.expect("couldn't get projects");
|
||||
let template = ProjectsTemplate {
|
||||
active_navbar: NavBar::PROJECTS,
|
||||
project_list: list.join("\n"),
|
||||
};
|
||||
HtmlTemplate(template)
|
||||
}
|
||||
|
|
@ -23,43 +30,51 @@ pub async fn projects() -> impl IntoResponse {
|
|||
#[template(path = "projects.html")]
|
||||
struct ProjectsTemplate {
|
||||
active_navbar: &'static str,
|
||||
project_list: String,
|
||||
}
|
||||
|
||||
async fn archserver() -> impl IntoResponse {
|
||||
let template = ArchServerTemplate {
|
||||
active_navbar: NavBar::PROJECTS
|
||||
};
|
||||
async fn project(
|
||||
state: Extension<AppState>,
|
||||
Path(params): Path<HashMap<String, String>>,
|
||||
) -> impl IntoResponse {
|
||||
let db_pool = &state.db;
|
||||
let project_id: &String = params.get("project").unwrap();
|
||||
let project: Project = match Project::read_by_reference(db_pool, project_id).await {
|
||||
Ok(a) => *a,
|
||||
Err(_) => panic!("Not an article at all!!!!"),
|
||||
};
|
||||
let footer: &str = "<a href=\"/projects\">Back to Projects</a>";
|
||||
let template = ArticleTemplate {
|
||||
active_navbar: NavBar::PROJECTS,
|
||||
content: project.content.expect("Should have had content if it got this far"),
|
||||
footer: footer.to_string(),
|
||||
};
|
||||
HtmlTemplate(template)
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "projects/ArchServer.html")]
|
||||
struct ArchServerTemplate {
|
||||
active_navbar: &'static str,
|
||||
}
|
||||
pub async fn get_projects_as_links_list(pool: &PgPool) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
let mut projects: Vec<Project> = match Project::read_all(pool).await {
|
||||
Ok(a) => a.iter().map(|x| *x.clone()).collect(),
|
||||
Err(_) => panic!("Not a project at all!!!!"),
|
||||
};
|
||||
|
||||
async fn tak() -> impl IntoResponse {
|
||||
let template = TakTemplate {
|
||||
active_navbar: NavBar::PROJECTS
|
||||
};
|
||||
HtmlTemplate(template)
|
||||
}
|
||||
projects.sort_by(|a, b| b.date.cmp(&a.date));
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "projects/Tak.html")]
|
||||
struct TakTemplate {
|
||||
active_navbar: &'static str,
|
||||
}
|
||||
|
||||
async fn ed() -> impl IntoResponse {
|
||||
let template = EdgeDetectionTemplate {
|
||||
active_navbar: NavBar::PROJECTS
|
||||
};
|
||||
HtmlTemplate(template)
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "projects/EdgeDetection.html")]
|
||||
struct EdgeDetectionTemplate {
|
||||
active_navbar: &'static str,
|
||||
let list: Vec<String> = projects
|
||||
.iter()
|
||||
.map(|project| {
|
||||
if project.blog {
|
||||
format!(
|
||||
"<li><a href=\"/blog/{}\">{}</a></li>",
|
||||
project.reference, project.title
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"<li><a href=\"/projects/{}\">{}</a></li>",
|
||||
project.reference, project.title
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Ok(list)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use axum::{
|
|||
use sqlx::PgPool;
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
use super::blog::get_articles_as_links_list;
|
||||
use super::{blog::get_articles_as_links_list, projects::get_projects_as_links_list};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
|
|
@ -39,15 +39,22 @@ pub fn get_router(pool: PgPool) -> Router {
|
|||
|
||||
async fn home(state: Extension<AppState>) -> impl IntoResponse {
|
||||
let db_pool = &state.db;
|
||||
let list: Vec<String> = get_articles_as_links_list(db_pool)
|
||||
let article_list: Vec<String> = get_articles_as_links_list(db_pool)
|
||||
.await
|
||||
.expect("couldn't get articles");
|
||||
|
||||
let (head, _) = list.split_at(5);
|
||||
let (article_head, _) = article_list.split_at(5);
|
||||
|
||||
let project_list: Vec<String> = get_projects_as_links_list(db_pool)
|
||||
.await
|
||||
.expect("Couldn't get projects");
|
||||
|
||||
let (project_head, _) = project_list.split_at(5);
|
||||
|
||||
let template = HomeTemplate {
|
||||
active_navbar: NavBar::HOME,
|
||||
recent_blogs: head.join("\n"),
|
||||
recent_blogs: article_head.join("\n"),
|
||||
recent_projects: project_head.join("\n"),
|
||||
};
|
||||
HtmlTemplate(template)
|
||||
}
|
||||
|
|
@ -57,6 +64,7 @@ async fn home(state: Extension<AppState>) -> impl IntoResponse {
|
|||
struct HomeTemplate {
|
||||
active_navbar: &'static str,
|
||||
recent_blogs: String,
|
||||
recent_projects: String,
|
||||
}
|
||||
|
||||
async fn now() -> impl IntoResponse {
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
<!-- prettier-ignore -->
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content">
|
||||
{{content|safe}}
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
{%if previous != ""%}<a href={{previous}}>previous</a>{%endif%}{%if previous != "" && next != ""%} / {%endif%}{%if next != ""%}<a href={{next}}>next</a>{%endif%}
|
||||
</footer>
|
||||
{% endblock %}
|
||||
12
templates/article.html
Executable file
12
templates/article.html
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
<!-- prettier-ignore -->
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content">
|
||||
{{content|safe}}
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
{{footer|safe}}
|
||||
</footer>
|
||||
{% endblock %}
|
||||
1
templates/blog_footer.html
Normal file
1
templates/blog_footer.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
{%if previous != ""%}<a href={{previous}}>previous</a>{%endif%}{%if previous != "" && next != ""%} / {%endif%}{%if next != ""%}<a href={{next}}>next</a>{%endif%}
|
||||
|
|
@ -44,12 +44,9 @@
|
|||
|
||||
<section id="projects">
|
||||
<h2>Projects</h2>
|
||||
<h3>Featured</h3>
|
||||
<h3>Recent</h3>
|
||||
<ul class="no-bul">
|
||||
<li><a href="/projects/archserver">Arch Server</a></li>
|
||||
<li><a href="/blog/srw">Speed Reading Workbook</a></li>
|
||||
<li><a href="/blog/tw">This Website</a></li>
|
||||
<li><a href="/projects/tak">Tak</a></li>
|
||||
{{recent_projects|safe}}
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
|
|
|
|||
|
|
@ -5,12 +5,7 @@
|
|||
<div id="content">
|
||||
|
||||
<ul class="no-bul">
|
||||
<li><a href="/projects/archserver">Arch Server</a></li>
|
||||
<li><a href="/blog/srw">Speed Reading Workbook</a></li>
|
||||
<li><a href="/blog/tw">This Website</a></li>
|
||||
<li><a href="/blog/lt">Leveling Tool</a></li>
|
||||
<li><a href="/projects/tak">Tak (Board Game)</a></li>
|
||||
<li><a href="/projects/ed">Edge Detection</a></li>
|
||||
{{project_list|safe}}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,282 +0,0 @@
|
|||
<!-- prettier-ignore -->
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content">
|
||||
<h2>
|
||||
Arch Server
|
||||
</h2>
|
||||
<p>
|
||||
The majority of the process to set up this server was followed from <a href="https://sive.rs/ti" target="_blank">here</a>.
|
||||
Here I will outline where I deviated and use workarounds to make all this work on Arch Linux.
|
||||
</p>
|
||||
|
||||
<br>
|
||||
<h3>Register a Domain -> Create SSH Key</h3>
|
||||
<ul class="list-decimal">
|
||||
<li>No changes</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>Create Your Server</h3>
|
||||
<ul class="list-decimal">
|
||||
<li>Step 6: Select Arch as operating system istead of OpenBSD</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>Attach Storage -> Point Your Domain Here</h3>
|
||||
<ul class="list-decimal">
|
||||
<li>No changes</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>SSH Into Root</h3>
|
||||
<ul class="list-decimal">
|
||||
<li>Step 9: command to update packages on Arch is <code>pacman -Syu</code> instead of <code>syspatch</code></li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>Create Your Username</h3>
|
||||
<ul class="list-decimal">
|
||||
<li>Step 2-16: <code>useradd -m <UserName></code> will automatically fill out all the defaults</li>
|
||||
<li>Step 17-20: <code>passwd <UserName></code> to set the users password.</li>
|
||||
<li>Step 21: <code>usermod -aG wheel,wheelnpw <UserName></code> to allow executing as root without password</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>Another User?</h3>
|
||||
<ul class="list-decimal">
|
||||
<li>No extra users added</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>Secure Your login</h3>
|
||||
<ul class="list-decimal">
|
||||
<li>Step 2: replace <code>doas</code> in command with <code>sudo</code></li>
|
||||
<li>Step 5: replace <code>rcctl</code> in command with <code>systemctl</code></li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>Format Storage</h3>
|
||||
<p>
|
||||
Here I deviated much more from the steps so I will write out the steps used without referring to Derek's original document.
|
||||
</p>
|
||||
<br>
|
||||
<ul class="list-decimal">
|
||||
<li><code>ssh <UserName>@yourdomain.name</code></li>
|
||||
<li><code>sudo su</code></li>
|
||||
<li><code>fdisk -l</code> will list the disks available, look for the one with the size of the blockstorage you added to your server.
|
||||
<ul class="ml-10 list-disc">
|
||||
<li>Likely /dev/vdb</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>cryptsetup -y -v --type luks2 luksFormat /dev/vdb</code>
|
||||
<ul class="ml-10 list-disc">
|
||||
<li>Will ask for storage name, I used blockstorage</li>
|
||||
<li>Enter your passphrase when asked and confirm it. Make sure you note this down somewhere, you will not be able to access your storage without it.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>dd if=/dev/zero of=/dev/mapper/blockstorage status=progress</code>
|
||||
<ul class="ml-10 list-disc">
|
||||
<li>This will write all 0s to the memory of the blockstorage and ensure that the data seen from the outside is random.</li>
|
||||
<li>It will take a long time to complete. status=progress will show you the progress and you will be able to tell when it has completed.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>mkfs.ext4 /dev/mapper/blockstorage</code> will create the filesystem on the disk</li>
|
||||
<li><code>mkdir /mnt/blockstorage</code> to create the mountpoint </li>
|
||||
<li><code>chown /mnt/blockstorage <UserName>:<UserName></code> to set ownership to your user</li>
|
||||
<li>Basic commands to manually manage the filesystem
|
||||
<ul class="ml-10 list-disc">
|
||||
<li>To mount the filesystem <code>sudo mount /dev/mapper/blockstorage /mnt/blockstorage</code></li>
|
||||
<li>To unlock the filesystem <code>sudo cryptsetup luksOpen /dev/vdb blockstorage</code></li>
|
||||
<li>To lock the filesystem <code>sudo cryptsetup luksClose blockstorage</code></li>
|
||||
<li>To unmount filesystem <code>sudo umount /mnt/blockstorage</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
<p>
|
||||
We don't need to deal with these complex commands each time. We can use aliasing to substitute much simpler commands to remember instead.
|
||||
</p>
|
||||
<br>
|
||||
<ul class="list-decimal">
|
||||
<li><code>echo "alias mntblk='sudo mount /dev/mapper/blockstorage /mnt/blockstorage' >> .bashrc"</code> will set the mounting command to <code>mntblk</code></li>
|
||||
<li><code>echo "alias unlkblk='sudo cryptsetup luksOpen /dev/vdb blockstorage' >> .bashrc"</code> will set the unlocking command to <code>unlkblk</code></li>
|
||||
<li><code>echo "alias lkblk='sudo cryptsetup luksClose blockstorage' >> .bashrc"</code> will set the locking command to <code>lkblk</code></li>
|
||||
<li><code>echo "alias umntblk='sudo umount /mnt/blockstorage' >> .bashrc"</code> will set the unmounting command to <code>umntblk</code></li>
|
||||
</ul>
|
||||
<br>
|
||||
<p>
|
||||
This will still need 2 commands to mount, and 2 more to unmount.
|
||||
I like having these aliased separately just in case I want to run one without the other for some reason.
|
||||
To bring this to 1 command for each I will make 2 more aliases using the ones I just created.
|
||||
You could make each of these 2 using the expanded commands above, but I find this way simpler to read.
|
||||
</p>
|
||||
<br>
|
||||
<ul class="list-decimal">
|
||||
<li><code>echo "alias m='mntblk && unlkblk' >> .bashrc"</code> will make <code>m</code> mount the system and unlock it</li>
|
||||
<li><code>echo "alias m-x='lkblk && umntblk' >> .bashrc"</code> will make <code>m-x</code> lock the filesystem and unmount it</li>
|
||||
<li>log out and log back in to be able to use these new aliases or run <code>source .bashrc</code></li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>Use Your Storage</h3>
|
||||
<ul class="list-decimal">
|
||||
<li>No changes</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>FreeFileSync</h3>
|
||||
<ul class="list-decimal">
|
||||
<li>I did not use this</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>Verify and Unmount</h3>
|
||||
<ul class="list-decimal">
|
||||
<li>No changes</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>Web Server</h3>
|
||||
<p>
|
||||
Ports are not open externally by defaul on Arch.
|
||||
<code>sudo ufw 80</code> will open the port for the web server.
|
||||
Do not open unnecessary ports.
|
||||
Arch is fully capable of hosting an apache server like is used in the guide.
|
||||
I prefer Nginx and so used that.
|
||||
The configuration is a little more complex than apache but I am more familiar with it and it works well for single site hosting.
|
||||
</p>
|
||||
<br>
|
||||
<ul class="list-decimal">
|
||||
<li>install with <code>sudo pacman -S nginx</code></li>
|
||||
<li>enable with <code>sudo systemctl enable nginx.service</code></li>
|
||||
<li>start with <code>sudo systemctl start nginx.service</code></li>
|
||||
</ul>
|
||||
<br>
|
||||
<p>
|
||||
When I was testing I was serving static content from /srv/http/domain.com/public_html.
|
||||
In the /etc/nginx/nginx.conf file in the http block I added:
|
||||
</p>
|
||||
<pre><code>
|
||||
server {:
|
||||
server_name yourdomain www.yourdomain
|
||||
location / {:
|
||||
root /srv/http/yourdomain/public_html
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<br>
|
||||
<p>
|
||||
Run <code>nginx -s reload</code> to reload the configuration file.
|
||||
</p>
|
||||
<p>
|
||||
Then the html files to be served can be put in whatever folder specified in root and will be served from there.
|
||||
In the end I have my website built and served using SvelteKit and Nginx is working as a proxy for that program.
|
||||
I will go into Svelte in the future.
|
||||
</p>
|
||||
<p>
|
||||
To have a secure webserver (https) I need a certificate.
|
||||
</p>
|
||||
<ul class="list-decimal">
|
||||
<li><code>sudo pacman -S certbot certbot-nginx</code> to install the certbot package and nginx plugin</li>
|
||||
<li><code>sudo certbot --nginx</code> will scan for nginx sites and create certificates</li>
|
||||
<li><code>sudo certbot renew</code> will renew all certificates on the machine managed by certbot.</li>
|
||||
</ul>
|
||||
<br>
|
||||
<p>
|
||||
To run this renewal periodically I made certref file in bin containing:
|
||||
</p>
|
||||
<pre><code>
|
||||
#!/bin/bash
|
||||
sudo certbot renew
|
||||
</code></pre>
|
||||
<p>
|
||||
Set permissions to 700 with <code>sudo chmod 700 bin/certref</code>, then added a cronjob to run the refresh once a month.
|
||||
</p>
|
||||
|
||||
<br>
|
||||
<h3>Simple Website</h3>
|
||||
<p>
|
||||
Can use exactly what is in the guide here.
|
||||
Just make sure the files are located in the root you specified in the Nginx configuration above.
|
||||
</p>
|
||||
|
||||
<br>
|
||||
<h3>File Sharing With Pub</h3>
|
||||
<p>
|
||||
Did not set this up with Nginx yet.
|
||||
If I do will update this.
|
||||
</p>
|
||||
|
||||
<br>
|
||||
<h3>Calendar and Contacts</h3>
|
||||
<p>
|
||||
This is very similar for both Arch and OpenBSD so will list changes
|
||||
Arch does not have htpasswd by default.
|
||||
Install with <code>sudo pacman -S apache</code> then continue.
|
||||
</p>
|
||||
<br>
|
||||
<ul class="list-decimal">
|
||||
<li>Step 2: sudo su</li>
|
||||
<li>Step 3: pacman -S radicale</li>
|
||||
<li>Step 9: systemctl enable radicale.service</li>
|
||||
<li>Step 10: systemctl start radicale.service</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>Android and Iphone + Test</h3>
|
||||
<ul class="list-decimal">
|
||||
<li>No changes</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>Backups</h3>
|
||||
<ul class="list-decimal">
|
||||
<li>Step 3: replace '/sh' with '/bash' and 'doas' with 'sudo' in the echo command.</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<h3>Email</h3>
|
||||
<p>
|
||||
I decided to use an external email provider (FastMail) instead of setting up the email verification myself.
|
||||
This was before the explanation for this was published in this article.
|
||||
May try again in the future but for now I am happy with my own email for a few dollars a month that I can move to a different provider if needed
|
||||
</p>
|
||||
|
||||
<br>
|
||||
<h3>Git</h3>
|
||||
<p>
|
||||
I use git extensively in my coding projects, configurations, and notes so I wanted to be able to host my own git server.
|
||||
</p>
|
||||
<br>
|
||||
<ul class="list-decimal">
|
||||
<li>assume root with <code>sudo su</code></li>
|
||||
<li>install with <code>pacman -S git</code></li>
|
||||
<li>in /etc/passwd file set the home directory for the git user to /srv/git</li>
|
||||
<li>created .ssh folder in /srv/git and added an ssh key like above to log into the server</li>
|
||||
<li>enabled with <code>systemctl enable git-daemon.socket</code></li>
|
||||
<li>started with <code>systemctl start git-daemon.socket</code></li>
|
||||
</ul>
|
||||
<br>
|
||||
<p>
|
||||
To make a new project I navigate to /srv/git and make a new project with <code>git -bare init <projectName>.git</code>.
|
||||
Set ownership to git user with <code>chown <projectName>.git git:git</code>.
|
||||
To set the remote origin for a project that is already initialized <code>git remote add origin git@url:/srv/git/<projectName></code>
|
||||
To clone a project from remote to local <code>git clone git://url/<projectName> localName</code>
|
||||
</p>
|
||||
|
||||
<br>
|
||||
<h2>Evolving</h2>
|
||||
<p>
|
||||
This project is slowly but constantly evolving.
|
||||
The above state is how it is running currently and that may change.
|
||||
I will endeavor to update it when things do.
|
||||
Slowly grow from something mostly taken from Derek's guide (thank you so much for that) to something uniquely my own and serving the my exact needs.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<a href="/projects">Back to Projects</a>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
<!-- prettier-ignore -->
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content">
|
||||
<h2>
|
||||
Edge Detection
|
||||
</h2>
|
||||
<p>
|
||||
This was a project that I did with two classmates for a parallel programming class.
|
||||
Source code <a href="https://github.com/Avi-1/cp631">here</a>.
|
||||
The programming was done in C except for a python script for image pre-processing (it was just much faster to write).
|
||||
</p>
|
||||
<p>
|
||||
Edge detection is usually done with convolutions.
|
||||
A convolution is a matrix of values (kernel) that detects the difference in colour density of pixels in an image.
|
||||
The kernel is overlayed on the pixel values in the image and each pixel value is multiplied by the value in the kernel above it.
|
||||
All the resulting values are added together into a single value.
|
||||
The design of the kernel dictates what sort of feature will be detected.
|
||||
A large resulting value indicates the presence of the desired feature.
|
||||
</p>
|
||||
<p>
|
||||
This is the sample input image we used for some of the testing.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="/assets/images/InputImage.png" alt="Input" />
|
||||
<figcaption>Input image</figcaption>
|
||||
</figure>
|
||||
<p>
|
||||
This image has a lot of both horizontal and vertical lines for detection.
|
||||
The image gets converted into grayscale during preprocessing for ease of use.
|
||||
</p>
|
||||
<p>
|
||||
We used Sobel Kernels as opposed to designing our own.
|
||||
These are very commonly used for edge detection.
|
||||
The larger the dimensions of the kernel the larger an area of the image it inspect.
|
||||
A smaller kernel will only find very well defined edges.
|
||||
3×3 was the smallest kernel that we used.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="/assets/images/Horizontal3x3.png" alt="Horizontal line 3x3 kernel" />
|
||||
<img src="/assets/images/Vertical3x3.png" alt="Vertical line 3x3 kernel" />
|
||||
<figcaption>3x3 Soebel Kernels (left:Horizontal, right:Vertical)</figcaption>
|
||||
</figure>
|
||||
<p>
|
||||
These kernels found the very defined edges on the sides of the trees and branches.
|
||||
This includes their reflections in the water below, though the reflection did dampen the edges a fair bit.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="/assets/images/Output3x3.png" alt="3x3 detection output" />
|
||||
<figcaption>3x3 Edge detection output</figcaption>
|
||||
</figure>
|
||||
<p>
|
||||
As you can see it picked up almost nothing from the sky in the image.
|
||||
We also tested kernels of size 5×5, 7×7, and 9×9.
|
||||
Here are the 9×9 kernels we used.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="/assets/images/Horizontal9x9.png" alt="Horizontal line 9x9 kernel" />
|
||||
<img src="/assets/images/Vertical9x9.png" alt="Horizontal line 9x9 kernel" />
|
||||
<figcaption>9x9 Soebel Kernels (left:Horizontal, right:Vertical)</figcaption>
|
||||
</figure>
|
||||
<p>
|
||||
These large kernels will detect differences in gradient across a much larger portion of the image at once.
|
||||
This results in a much more textured image.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="/assets/images/Output9x9.png" alt="9x9 detection output" />
|
||||
<figcaption>9x9 Edge detection output</figcaption>
|
||||
</figure>
|
||||
<p>
|
||||
As you can see the trees are almost a uniform mass of edges.
|
||||
The reflections less so but still very dense.
|
||||
It did also manage to pick up the texture of the clouds.
|
||||
Different kernels are good at detecting different types of edges.
|
||||
A smaller kernel will be much better for sharp edges and will ignore the rest.
|
||||
A larger kernel will get much less well defined edges.
|
||||
</p>
|
||||
<p>
|
||||
There is also a large difference in the speed of the program, a larger kernel has significantly more operations that need to be performed.
|
||||
After the application of the convolutions the rest of the project was parallelizing this process in an effort to speed it up.
|
||||
We used both Message Passing Interface (MPI) and CUDA for parallelization in order to compare them.
|
||||
MPI uses the CPU and so is limited by the number of cores the CPU of the device has.
|
||||
CUDA uses the graphics card so has many more cores to work with but has more overhead.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="/assets/images/ConvolutionTime.png" alt="Convolution time graph" />
|
||||
</figure>
|
||||
<p>
|
||||
Unless you have access to a large number of CPU cores CUDA is almost always better.
|
||||
For a general application as long as you have access to a graphics card CUDA is the way to go.
|
||||
</p>
|
||||
<p>
|
||||
This project was very enlightening as to how the underlying processes for image recognition work.
|
||||
I have done a little bit of machine learning with convolutional neural networks, but for most libraries this portion is already done for you.
|
||||
Understanding what is going on under the hood was very interesting.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<a href="/projects">Back to Projects</a>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
<!-- prettier-ignore -->
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content">
|
||||
<h2>
|
||||
Tak
|
||||
</h2>
|
||||
<p>
|
||||
One of my favourite fantasy book series is the Kingkiller Chronicles by Patrick Rothfuss.
|
||||
In the second book The Wise Man’s Fear there is a board game that they play that sounded fascinating.
|
||||
I have always like abstract strategy games and this was his worlds “Chess”; the game that had been around forever, simple rules but deep strategy etc.
|
||||
</p>
|
||||
<p>
|
||||
I was so excited when I found out that it was being made into a full game by <a href="https://cheapass.com/tak/">Cheapass Games</a>.
|
||||
The rules are available for free on their website.
|
||||
I bought myself a copy and it is a lot of fun.
|
||||
They did an amazing job getting the feel of the game right and it is now for sale on <a href="https://worldbuildersmarket.com/collections/tak-a-beautiful-game">WorldBuilders Market</a>.
|
||||
Proceeds from WorldBuilders go to charity so if you try out the game and enjoy it please consider buying a copy.
|
||||
</p>
|
||||
<p>
|
||||
As a project I made a digital copy of the game.
|
||||
Source code <a href="https://github.com/OrthogonalStar/Tak-BoardGame">here</a>.
|
||||
It is written in python 3 and the only extra library it needs to run is pygame.
|
||||
If you want to run the tests you will need pytest as well.
|
||||
</p>
|
||||
<p>
|
||||
Here is the program interface.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="/assets/images/TakProgram.png" alt="Home Board" />
|
||||
<figcaption>Tak Program</figcaption>
|
||||
</figure>
|
||||
<p>
|
||||
The game was a very interesting challenge to implement for a few reasons.
|
||||
</p>
|
||||
<p>
|
||||
The board size is flexible.
|
||||
It ranges from 3×3 to 8×8 (skipping 7×7 but I am not quite sure why).
|
||||
The rules for victory are the same regardless of board size but the number of pieces each player has access to changes.
|
||||
The board had to be housed in a flexible object that would also dictate the number of pieces.
|
||||
</p>
|
||||
<p>
|
||||
While there are only two types of pieces (regular and capstones), the standard pieces can be played in 2 different orientations.
|
||||
Flat placement is the default.
|
||||
It contributes to win conditions and can make stacks.
|
||||
Placing a normal piece as a wall blocks other pieces but does not contribute to your win condition and can only be at the top of a stack.
|
||||
The capstone has the best characteristics of both and can squish walls to flat if it lands on them alone.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="/assets/images/TakPieces.png" alt="Pieces" />
|
||||
<figcaption>Tak pieces (left to right: flat, wall, capstone)</figcaption>
|
||||
</figure>
|
||||
<p>
|
||||
Rather than make an object for each piece I built the piece specific rules into the move action.
|
||||
The move is the same at its core for each piece and the only difference is the interaction when one piece lands on top of another.
|
||||
Storing the pieces as integers in a 2d array makes this far simpler.
|
||||
As the interactions depend on both pieces, using an integer makes that comparison very easy.
|
||||
</p>
|
||||
<p>
|
||||
The game has an interesting 3 dimensional component.
|
||||
The pieces can be stacked on top of one another and a stack can move as far as the stack is high (in a straight line controlled by the player whose piece is at the top) dropping one piece at a minimum on each square along the way.
|
||||
This means that each square in the 2d array needs to contain a stack where new pieces are added to the top.
|
||||
When pieces are lifted off a stack your hand acts like a second stack.
|
||||
Pieces are popped off the top of the square and added to the stack in your hand.
|
||||
Then they are dropped from the “bottom” of this inverted stack as you move.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="/assets/images/CurrentMove-2.png" alt="Move view" />
|
||||
<img src="/assets/images/CurrentDrop-2.png" alt="Drop view" />
|
||||
</figure>
|
||||
<p>
|
||||
The comparison needs to be done from the “bottom” of the inverted stack in your hand to the top of the stack that the piece will land on.
|
||||
</p>
|
||||
<p>
|
||||
That is the main challenge of the game play.
|
||||
The victory conditions were also challenging.
|
||||
The game has 3 different ways that it can end and 2 possible ways to calculate who won.
|
||||
|
||||
</p>
|
||||
<p>
|
||||
The game ends when one player builds a connected line of pieces from one of the sides of the board to the opposite side.
|
||||
The line does not need to be straight but squares do not connect on the diagonal.
|
||||
The victor in this case is the player who has the “road”.
|
||||
If a move creates a road for both players the active player wins.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="/assets/images/Victory.png" alt="Victory" />
|
||||
<figcaption>Victory!</figcaption>
|
||||
</figure>
|
||||
<p>
|
||||
The game also ends if either player runs out of pieces or there are no more open spaces on the board.
|
||||
In that case the winner is the player with the control of the most squares of the board.
|
||||
</p>
|
||||
<p>
|
||||
Both of these need to be checked after each turn and when a winner is found the points are calculated based on the size of the board and the number of pieces left.
|
||||
</p>
|
||||
<p>
|
||||
Compared to Chess or Go, Tak is a little bit more complicated.
|
||||
This is not through having a significantly large number of rules but the systems intertwine more and are more flexible.
|
||||
This does not mean that it is a better game just different, but it has a similar feeling of depth and timelessness.
|
||||
</p>
|
||||
<p>
|
||||
The board being stored as an array of integers means that it takes very little memory.
|
||||
That made it simple to store past boards in a stack and implement an undo function.
|
||||
Both to take back moves and even go back to a previous game from the same session.
|
||||
</p>
|
||||
<p>
|
||||
I am pleased with how the game turned out.
|
||||
I am working on other things at the moment but in the future I would like to do two more things with this game.
|
||||
I want to implement it online so that I can play with friends over the internet.
|
||||
I would also like to try and develop an A.I.
|
||||
agent to play the game.
|
||||
I had this in mind when I was writing it so made sure that the GUI was disconnected from the actual mechanics behind the game.
|
||||
This will hopefully make it easier to connect to an agent for training.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<a href="/projects">Back to Projects</a>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
Loading…
Reference in a new issue