Projects now using article template everywhere

This commit is contained in:
Awstin 2024-05-17 14:55:30 -04:00
parent 993181ba0d
commit fa6b030d98
12 changed files with 98 additions and 580 deletions

View file

@ -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)
}

View file

@ -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,
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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
View file

@ -0,0 +1,12 @@
<!-- prettier-ignore -->
{% extends "base.html" %}
{% block content %}
<div id="content">
{{content|safe}}
</div>
<footer>
{{footer|safe}}
</footer>
{% endblock %}

View file

@ -0,0 +1 @@
{%if previous != ""%}<a href={{previous}}>previous</a>{%endif%}{%if previous != "" && next != ""%} / {%endif%}{%if next != ""%}<a href={{next}}>next</a>{%endif%}

View file

@ -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>

View file

@ -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>

View file

@ -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 -&gt; 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 -&gt; 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 &lt;UserName&gt;</code> will automatically fill out all the defaults</li>
<li>Step 17-20: <code>passwd &lt;UserName&gt;</code> to set the users password.</li>
<li>Step 21: <code>usermod -aG wheel,wheelnpw &lt;UserName&gt;</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 &lt;UserName&gt;@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 &lt;UserName&gt;:&lt;UserName&gt;</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' &gt;&gt; .bashrc"</code> will set the mounting command to <code>mntblk</code></li>
<li><code>echo "alias unlkblk='sudo cryptsetup luksOpen /dev/vdb blockstorage' &gt;&gt; .bashrc"</code> will set the unlocking command to <code>unlkblk</code></li>
<li><code>echo "alias lkblk='sudo cryptsetup luksClose blockstorage' &gt;&gt; .bashrc"</code> will set the locking command to <code>lkblk</code></li>
<li><code>echo "alias umntblk='sudo umount /mnt/blockstorage' &gt;&gt; .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 &amp;&amp; unlkblk' &gt;&gt; .bashrc"</code> will make <code>m</code> mount the system and unlock it</li>
<li><code>echo "alias m-x='lkblk &amp;&amp; umntblk' &gt;&gt; .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 &#123:
server_name yourdomain www.yourdomain
location / &#123:
root /srv/http/yourdomain/public_html
&#125;
&#125;
</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 &lt;projectName&gt;.git</code>.
Set ownership to git user with <code>chown &lt;projectName&gt;.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/&lt;projectName&gt;</code>
To clone a project from remote to local <code>git clone git://url/&lt;projectName&gt; 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 %}

View file

@ -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 %}

View file

@ -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 Mans 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 %}