This commit is contained in:
Ellpeck 2019-12-27 23:23:04 +01:00
commit 28740f29e8
50 changed files with 2250 additions and 219 deletions

7
.gitignore vendored
View file

@ -1,2 +1,7 @@
node_modules
package-lock.json
package-lock.json
sitemap.xml
feed.json
rss.xml
atom.xml
blog-*.html

29
.htaccess Normal file
View file

@ -0,0 +1,29 @@
Options -Indexes
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule (.*) $1.html [L]
RewriteRule ^discord/?$ "https://discord.gg/Uy2ZM9X" [R=301,L]
RewriteRule ^actaddchangelog/?$ "https://github.com/Ellpeck/ActuallyAdditions/blob/master/update/changelog.md" [R=301,L]
RewriteRule ^actadddownload/?$ "https://minecraft.curseforge.com/projects/actually-additions/files" [R=301,L]
RewriteRule ^actaddlicense/?$ "https://github.com/Ellpeck/ActuallyAdditions/blob/master/LICENSE.md" [R=301,L]
RewriteRule ^actadd/?$ "https://minecraft.curseforge.com/projects/actually-additions" [R=301,L]
RewriteRule ^projects/?$ "https://ellpeck.de/#projects" [NE,R=301,L]
RewriteRule ^impressum/?$ "https://ellpeck.de/#impressum" [NE,R=301,L]
RewriteRule ^mc/?$ "https://ellpeck.de/minecraft-stuff" [R=301,L]
RewriteRule ^minecraft-stuff/?$ "https://ellpeck.de/projects" [R=301,L]
RewriteRule ^yt/?$ "https://www.youtube.com/c/ellpeck" [R=301,L]
RewriteRule ^wishlist/?$ "https://www.amazon.de/hz/wishlist/ls/LZO9Y2Z3VJ5Q?&sort=default" [R=301,L]
RewriteRule ^commissions/?$ "https://ellpeck.de/#commissions" [NE,R=301,L]
RewriteRule ^fftranslate/?$ "https://poeditor.com/join/project/ElzC23ecB6" [NE,R=301,L]
ErrorDocument 404 /404.html
<FilesMatch "^(blog|sitemap|feed|atom|rss)">
ExpiresActive On
ExpiresDefault A1
Header append Cache-Control must-revalidate
</FilesMatch>

View file

@ -69,7 +69,8 @@
'The world is quiet here.',
"Instructions unclear, got site stuck in debug mode",
"Please fix",
"If we get this video to 400 likes, I'll put the site back up"
"If we get this video to 400 likes, I'll put the site back up",
"how could this happen to me"
];
let message = Math.floor(Math.random() * messages.length);

97
blog/java_1.md Normal file
View file

@ -0,0 +1,97 @@
So you want to learn to code. In my opinion, a good language for beginners is Java, because it's a rather easy to understand language, and it's cross-platform, meaning it can run on any operating system without the need for the programmer (you!) to change anything based on the platform. Also, Java is object-oriented (more on that later), meaning it's pretty good for writing any sort of simple or complex program.[^1]
So let's start!
# Setting it up
To actually be able to compile[^2] any Java code, you first need to download the [Java Development Kit](https://jdk.java.net/), or JDK for short. If you're a Linux or Mac user, you can just follow [their installation tutorial](https://openjdk.java.net/install/), and if you're on Windows, you can follow [this tutorial from Stack Overflow](https://stackoverflow.com/a/52531093). To check if the installation worked, you can just type `java -version` into a command prompt (which you can open on Windows by just typing `cmd` into your search).
Some people might disagree with me on this, but I recommend using an Integrated Development Environment (IDE) for development *especially* when you're just starting out. An IDE is a program that helps you with coding by suggesting changes, notifying you of errors in your code and also allows you to easily run and debug your application. My personal suggestion would be to install [IntelliJ IDEA](https://www.jetbrains.com/idea/), but you could also use other IDEs like [Eclipse](https://www.eclipse.org/downloads/).
The next thing you'll have to do is create a new project inside your IDE. For IntelliJ, all you have to do is click `New Project`, select `Java`, click `Next` twice and then type your project's name. I'm going to go with `JavaTutorial` for mine.
# Hello World
Once that's done, we can finally start programming. At first, you'll find that a lot of the stuff we're doing isn't explained directly, but I'll come back to some of the things I'm showing you now at a later point. Things like classes, methods and parameters probably won't make sense to you yet, but using them is required for even the most basic of running programs.
The first thing we want to do is create a simple program that outputs something back to the user inside of the program's console. To start, simply right click your `src` folder and select `New` and `Java Class` and give it a name like `Main`. I'll show you the code for it first and then give you a quick rundown of the important parts you need to know about it for now.
```java
public class Main {
public static void main(String[] args) {
System.out.println("Hello Tutorial World!");
}
}
```
That's it! If you now run your program (for IntelliJ, there should be a small green arrow to the left that you can click on), it'll print out the message to the console and then stop the program.
A lot of the code you see above doesn't *really* matter to you right now. The important stuff is this:
- Each instruction, each thing that the program should do, ends with a semicolon `;` and usually a new line, though that is optional.
- Any instruction you write between the inner curly braces `{}` will be executed in order of how it's written.
- `System.out.println("<enter anything here>")` is an instruction that causes a line of text to be written to the console.
As a little exercise, you can make your program output another line of text:
```java
public class Main {
public static void main(String[] args) {
System.out.println("Hello Tutorial World!");
System.out.println("This is another line of text!");
}
}
```
# Variables
Now the first thing you'll probably want to do is learn what variables are and how to use them. A variable, simply put, is any kind of value (a number, a string of text etc.) that can be changed and that has a name. In Java, each variable also has to have a fixed type: While a variable's *value* can be changed (say, from `some text` to `some other text`), its *type* cannot be changed (so a variable representing text cannot be changed into a variable representing a number). Variables are created as follows:
```java
public class Main {
public static void main(String[] args) {
System.out.println("Hello Tutorial World!");
System.out.println("This is another line of text!");
String someText;
int someNumber;
someText = "Some Text";
someNumber = 10;
}
}
```
As you can see from line 6 and 7, to declare a variable, you need to put the variable's type first (`String` meaning text, `int` meaning an integer; a number without a decimal point), followed by the variable's name, which you can choose yourself.
For now, these variables don't have a value assigned to them yet. That's done in line 9 and 10. To assign a value to a variable, you first put the variable's name, followed by an equals sign `=`, followed by your desired value. For numbers, you can simply put them down, but text has to be wrapped in quotes `""` (which is also why the text in lines 3 and 4 is wrapped in quotes).
To actually do something with the variables, let's have them be printed out to the console. *I've omitted the rest of the code here because it's getting quite long, but I'm just adding the following lines inside the inner braces `{}` below the already written code.*
```java
System.out.println(someText);
System.out.println(someNumber);
```
As you can see, to print a variable's value to the console, all you have to do is put its name where you would usually put a piece of text directly.
# Variable Manipulation
Obviously, this isn't really that exciting yet: Our program isn't really doing anything so far. Let's manipulate our variables. I've added the following code:
```java
someNumber = someNumber + 5;
System.out.println(someNumber);
```
As you can see, you can use a plus sign `+` (as well as other mathematical operators) to do math with numbers. The first statement changes `someNumber`'s value to a new value: `someNumber + 5`. Running the program will show you that the result is pretty obvious: 15 is printed, because 10 + 5 = 15.
The same kind of manipulation can be done with text, which is especially useful for printing variables to the console. The plus sign `+` chains two strings of text together.
```java
String chainedText = "The resulting value is " + someNumber;
System.out.println(chainedText);
```
Here, a new variable is created (`String chainedText`) and its value is set (`=`) to the shown string plus the value of the `someNumber` variable. When added to the rest of the code, running this code should display `The resulting value is 15`.
# Conclusion
Now obviously, this is just the beginning of what you can learn and do with Java. I feel like this is a good point to finish the first part of this tutorial. I know it's probably not super interesting yet, but bear with me for a little while and you'll pretty quickly be able to do some pretty cool stuff.
Before you click away, I'd like to ask you some questions that you can answer by clicking on the discussion link below this post:
- Could you follow this tutorial?
- What do you think about the pacing? Is it too fast or too slow, are there too many examples or too few?
- Do you want me to actually continue this tutorial series?
Anyway, thanks a lot for reading and happy coding! <3
***
[^1]: Also, it's what Minecraft mods are written in, which is probably the reason most of you are here.
[^2]: Compiling is what occurs when code written by you is converted into something that the computer can actually understand. More on that later, probably.

163
blog/java_2.md Normal file
View file

@ -0,0 +1,163 @@
If you're reading this, then I assume you have already read the first part of this tutorial series, in which we covered setting up a program, making it print "Hello World" to the console, as well as how to declare and set the values of variables.
Today, we'll be talking about conditions and loops.[^1] As with the last tutorial, I'll be writing most of the code snippets first and then explaining what exactly they mean and do right after.
# The `if` condition
Let's say you want to create a program that checks if a given number `i` is greater than or equal to 27. Well, you do it like this:
```java
public class Main {
public static void main(String[] args) {
// This is where the code from the first tutorial would be
// if I hadn't deleted it for visual clarity
// Also, side note: You can use "//" to create comments:
// lines that aren't interpreted as code.
int i = 15;
if (i >= 27) {
System.out.println("i is greater than or equal to 27!");
}
}
}
```
So what we see here is called the `if` condition. It's structured as follows: You write the word `if`, followed by *the condition*, which is wrapped in parentheses `()`. You can then open curly braces `{}`, and any instructions that are written between them will only be executed if your supplied condition holds true.
The condition can be any statement that can either be `true` (correct) or `false` (incorrect). In this case, we're comparing two numbers with each other; there are several other ways to compare two numbers: `>`, `<`, `>=`, `<=`, `!=` and `==`, where the last two mean "not equal" and "exactly equal".[^2]
An important thing to note at this point is that this behavior is *different* with `String` variables.[^3] Comparing if two strings are equal by using `==` will not result in the behavior you might expect. Instead, you should compare two strings using `equals()` as follows:
```java
String s1 = "This is some text";
String s2 = "This is also some text";
if (s1.equals(s2)) {
System.out.println("s1 and s2 are equal!");
}
```
## The `boolean` type
A condition like this (which can either be `true` or `false`) can also be stored in a `boolean` variable[^4] whose state can then be checked using a similar `if` statement:
```java
int i = 15;
boolean i27 = i >= 27;
if (i27) {
System.out.println("i is greater than or equal to 27!");
}
```
The above code has the same effect as the code we looked at before. The cool thing about this method is that you can easily check if a condition *doesn't* hold true by simply adding an exclamation point `!` in front of any boolean variable:
```java
if (!i27) {
System.out.println("i is NOT greater than or equal to 27!");
}
// As you can see, this is especially useful when comparing strings
String s1 = "This is some text";
String s2 = "This is also some text";
if (!s1.equals(s2)) {
System.out.println("s1 and s2 are NOT equal!");
}
```
Additionally, two boolean variables (or simply two conditions) can be chained together in two ways:
```java
int i = 15;
if (i == 12 || i == 13) {
System.out.println("i is either 12 or 13!");
}
if (i >= 10 && i < 20) {
System.out.println("i is somewhere between 10 and 20");
}
```
As you can see in lines 2 to 4, two pipes `||` represent the `OR` operator: A combined condition using it holds true if the left side is true, or the right side is true, or both sides are true.
As you can see in lines 5 to 7, two ampersands `&&` represent the `AND` operator: A combined condition using it holds true if *both* sides are also true.
You can also wrap your checks in parentheses `()` to create a more complex condition, similarly to how you would use them in a mathematical expression:
```java
int j = 10;
int k = 15;
boolean combination = (j == 12 && k == 13) || (j == 10 && k == 25);
System.out.println(combination);
```
The above code will cause `true` to be printed if `j` equals 12 and `k` equals 13 *or* if `j` equals 10 and `k` equals 25. Otherwise, `false` will be printed.
## `else`
If you want something *else* to happen if a certain condition doesn't hold true, then you can do something like the following:
```java
int i = 15;
if (i >= 27) {
System.out.println("i is greater than or equal to 27!");
} else {
System.out.println("i is less than 27 :(");
}
```
As you can see, writing `else` after any `if` statement causes the code contained in the following curly braces `{}` to only be executed if the `if` statement's condition *isn't* true.
Optionally, you can also combine the `else` with another `if` statement to check for a different condition. You can do this as many times as you want:
```java
int i = 15;
if (i >= 27) {
System.out.println("i is greater than or equal to 27!");
} else if (i <= 10) {
System.out.println("i is less than 27, but also less than or equal to 10");
} else {
System.out.println("i is somewhere between 11 and 26");
}
```
# The `for` loop
The `if` condition is already pretty powerful, because you can execute different code based on different situations. Another useful thing is the `for` loop. How it's written and what it does can be a bit complicated to understand at first, but I'll try to break the following example down for you:
```java
for (int index = 0; index < 3; index = index + 1) {
System.out.println("The current index is " + index);
}
```
This specific `for` loop causes the following to be printed to the console:
```
The current index is 0
The current index is 1
The current index is 2
```
As you might be able to tell from that, a `for` loop causes any number of instructions in it to be executed a certain amount of times.
Its structure is pretty similar to the `if` statement's: First, you write the word `for`, followed by some loop instructions inside parentheses `()`, and then you open curly braces `{}` which contain the instructions that should be executed multiple times. The loop instructions contain three parts, which are separated by semicolons `;`:
- `int index = 0;` is the declaration of the *loop variable*: This is the instruction that will be executed *before* the loop starts running.
- `index < 3;` is the *loop's condition*: Before every time the loop's content is run, a check is done to make sure that this condition is still true. If it's not true anymore, the loop stops running.
- `index = index + 1` is the instruction that is executed *after* every time the loop's content has finished running.
So in the case of our loop, the following things happen:
- The `index` variable is declared and set to 0.
- `index < 3` is checked, which is obviously true.
- The loop's content is run, which causes the print to the console.
- `index = index + 1` is executed, which sets `index` to 1.
- `index < 3` is checked, which is still true.
- The loop's content is run again, and so on.
## `break`
If you want to exit a `for` loop before its condition turns to `false`, you can use the `break;` statement as follows:
```java
for (int index = 0; index < 10000; index = index + 1) {
System.out.println("The current index is " + index);
if (index >= 2) {
break;
}
}
```
This loop has the same effect as the one we looked at before. Despite the loop condition specifying that it should repeat 10000 times, it is only repeated three times, because the condition on line 4 is evaluated every time and the loop is forced to stop running once it is true.
# Conclusion
In this tutorial, you (hopefully) learned what the `if` and `for` statements are and how to use them. By now, you should be able to write some more complicated code. As a little exercise, you can try to solve the following problem using the things you learned in the last two tutorials:
> Given a variable `i` and another variable `exp`, calculate if the result of `i`<sup>`exp`</sup> (`i` to the power of `exp`) is less than or equal to the value of another variable `j` (and print the result to the console accordingly).
If you're stuck, you can check out my solution [here](https://gist.github.com/Ellpeck/999bafe352f84d5e1f09750e026d5bbf).
Thanks for reading this tutorial and I hope it helped you out! If you have any feedback or questions about it, you can click the discussion link below and ask me on Twitter, or join my Discord server using the widget on the main page. Happy coding!
***
[^1]: I'm covering this topic *before* I cover what exactly classes and methods are. I think that conditions and loops take importance here, because they're used broadly in every language as well as most programs, whereas object orientation is a feature specific to some languages, as well as specific to more "complex" programs. It's also somewhat complicated, and I want to explain it right, because when I learned Java, I didn't even remotely understand it correctly at first.
[^2]: Note that, when *comparing* two numbers, two equals signs `==` are used. This is different from *assigning a value* to a variable, which only uses one equals sign `=`.
[^3]: Why exactly this is the case will be discussed later when we get into object orientation. The different behavior mentioned here is the case for any variable types which aren't so-called *native types*. Any variable whose type starts with an uppercase letter is not a native type, because it derives from a *class*.
[^4]: Named after [George Boole](https://en.wikipedia.org/wiki/George_Boole), a mathematician.

175
blog/java_3.md Normal file
View file

@ -0,0 +1,175 @@
I've been thinking about how to structure this tutorial a lot, and I decided to teach you all about methods *before* I get into object orientation, so you'll have to wait a little while longer before we get into the real nitty-gritty. To understand this one, you should've already read and understood tutorials 1 and 2. If you haven't, you can get to them by clicking the "Previous Post" button in the navigation bar.
So, here goes!
# Methods
Methods are a way, in programming, to move certain code into a different location where it's more organized and, more importantly, where it can be called multiple times from multiple locations, possibly with different data. You'll see what exactly that means in a minute.
Let's create a simple method in our main class:
```java
public class Main {
public static void main(String[] args) {
// I've omitted the code from the previous tutorials for readability
}
public static void printInfo() {
System.out.println("This is some important information!");
}
}
```
Let's take a look at the `printInfo` method I created. In this tutorial, we'll only be talking about `public` and `static` methods, so you'll just have to take that part for granted for now. Following that, you write `void` and then the name of your method (which can be anything you want), followed by parentheses `()` and then braces `{}`. Similarly to the `if` statement and the `for` loop, the method's content goes between those two curly braces, as I have done in this example with the print statement.
As you can see, all a method is is pretty much a collection of code. At this point, you might've already noticed that that is exactly what the `main` structure we've previously been writing all of our code into is: `main` is just another method.
To *call* a method, that is to have the code inside of it be executed, all you have to do is write the following:
```java
public class Main {
public static void main(String[] args) {
// I've omitted the code from the previous tutorials for readability
printInfo();
}
public static void printInfo() {
System.out.println("This is some important information!");
}
}
```
There you go.
## Variables
It should be noted at this point that variables which are declared *inside* of a method, like the ones we've been using in the first two tutorials, are known as `local variables`. What this means is that they only exist *inside* of the method they are declared in. Check out this example:
```java
// This method declares a variable i that is set to 0
public static void methodOne() {
int i = 0;
}
// This method declares a *different* variable i that is set to 1
public static void methodTwo() {
int i = 1;
}
// This method will cause an error if you paste it into your IDE:
// You cannot declare two variables with the same name in one method.
public static void erroringMethod() {
int i = 0;
int i = 1;
}
```
This same behavior also counts for `if`, `for` and any other curly braces `{}` that you see: They close off any variables which are created inside of them and make them available to only that location.
# Parameters
Now you might be wondering what the point of methods is if all they do is execute a predefined list of code. Well... you can actually make a method *accept* a set of data that they can use to do their processing. The data given to a method is called *parameters*, and these parameters are simply variables that you declare between the method's parentheses `()` right after its name.
Let's take the following example:
```java
public static void printInfo(String strg) {
System.out.println("This is some important information about " + strg);
}
```
I modified the method by adding the parameter `strg` to it, which is of the type `String`. What this means is that now, when calling the method, it expects you to give it a string that it can use to do stuff with (in this case, print it out).
If you add that parameter to the code we previously wrote, you might notice that your IDE is now displaying an error to you. You can't just do this anymore:
```java
public static void main(String[] args) {
printInfo();
}
```
As we just said, `printInfo` now wants us to give it a string whenever we call it. To give it that string, simply put it between the parentheses `()` of your method call like this:
```java
String someString = "Some String";
printInfo(someString);
// or, optionally, the shorter form:
printInfo("Some String");
```
Running your code now should cause `This is some important information about Some String` to be displayed in your console.
In case you're thinking "I've seen that somewhere before", then you would be correct: This is exactly the same thing you do when you try to print something out to the console: You call the `println` method and give it the text you want to print out as the parameter.
The cool thing is that a method can also accept *multiple* parameters, so multiple bits of data can be passed into it when calling it. Let's look at this example:
```java
public class Main {
public static void main(String[] args) {
printInfo("Some String", 5);
}
public static void printInfo(String strg, int amount) {
for (int i = 0; i < amount; i = i + 1) {
System.out.println("This is some important information about " + strg);
}
}
}
```
As you can see, the `printInfo` method now takes two parameters, separated by a comma `,`. The code inside of the method should already be familiar to you: It's a simple for loop that prints the same message `amount` times.
So now, we have something that also demonstrates pretty well the versatility of methods: We can now call `printInfo` with any information that will be printed any amount of times:
```java
printInfo("Some String", 5);
printInfo("Some other String", 10);
```
# Returning
Another thing that methods can do that is really useful is the ability to *return* certain data. What this means is that they can, after their execution finishes, take the data they created or modified and give it back to the caller:
```java
public class Main {
public static void main(String[] args) {
int tenSquared = square(10);
System.out.println(tenSquared); // prints 100
System.out.println(square(5)); // prints 25
}
public static int square(int i) {
return i * i;
}
}
```
As you can see, I created a method `square` that returns its parameter `i`, but squared. To make a method return a value, two things have to be done:
- The method's *return type* has to be declared. For methods that don't return anything, the return type is `void`. For methods that return something, just replace `void` with the *type of variable* that it returns.
- To actually return a specific value after it has been calculated, just type `return`, followed by the value and a semicolon `;`.
Now, this method can be used as if it were just another number: We can set variables to it and print it out. But instead of it being just another number, it actually executes the code inside it every time.
## Stopping execution
A thing that should be noted about returning is that any code that comes after a return statement will *not* be executed. In other words: After returning a value, a method will stop its execution, no matter what comes next.
```java
public static int square(int i) {
return i * i;
System.out.println("This will never be called");
}
```
In this specific case, that isn't really useful - why write code that can never be executed? But take this other example:
```java
public static boolean isIGreaterThanJ(int i, int j) {
if (i > j) {
return true;
}
System.out.println("i is NOT greater than j!");
return false;
}
```
In this example, the print statement will not be executed if `i` is greater than `j` despite the fact that it's not wrapped in an `else`, because the method gets returned out of before it can be called.
## Requirements
If you create a method that has a return type other than `void` (which, again, means "this method doesn't return anything"), then every possible path that the execution can take inside the method *needs* to return *something*.
What this means is that you can't have something like this:
```java
public static boolean isIGreaterThanJ(int i, int j) {
if (i > j) {
return true;
}
System.out.println("i is NOT greater than j!");
}
```
Because when `i` is *not* greater than `j`, the print statement will be executed but then, the method doesn't know what value to return, so this code will give out an error before it even tries to execute.
# Conclusion
So yea, that's pretty much everything important there is to know about (static) methods. In one of the next tutorials, we'll finally get into actual object orientation and we'll soon be taking a look at non-static methods, which are even more useful than static methods.
As a little exercise, you might want to create some methods with different return types and different parameters and call them. Have some fun!
I hope you enjoyed reading, and of course: Happy coding!

209
blog/java_4.md Normal file
View file

@ -0,0 +1,209 @@
So you've gotten far enough into Java now that you'll finally be able to learn about what this "object orientation" jazz is that everyone keeps talking about. I feel like this is a somewhat complicated topic to explain, so bear with me.
# Classes
So far in the tutorial, we've only ever used classes as basic storage containers for variables and methods. Let's stick with that for another moment while we create a simple `Utility` class that has a method we might want to use throughout our program. We'll create it in a new file (Right-Click on `src`, `New`, `Java Class` in IntelliJ) like so:
```java
public class Utility {
}
```
As you can see, this structure is similar to that of our `Main` class: The word `public`, followed by the word `class`, followed by the name of the class (which, in 99% of cases, should match the file name), followed by curly braces `{}`, which, as you might expect by now, contain the content of our class.
So let's add a method to it that, I don't know, returns the absolute value of an integer passed to it. We'll call this one `abs`, for "absolute".[^1]
```java
public class Utility {
public static int abs(int num) {
if (num < 0) {
return -num;
} else {
return num;
}
}
}
```
By now, this code should be pretty easy for you to understand: We've added a method called `abs` that takes in an integer parameter called `num`, and if `num` is negative, then we return the negated value of num (`-num`).
Now, because this method is in a different class to the one we might want to use the method in (`Main` in our case), we have to explicitly mention the class the method is in by prefixing the class' name followed by a dot `.` when calling it, like so:
```java
public class Main {
public static void main(String[] args) {
// This is our usual main method
int abs = Utility.abs(-17);
System.out.println(abs);
}
}
```
# Objects
Let's finally move away from `static` methods and into object orientation.[^2] I'll give you a quick, made-by-me outline of what object orientation even *means*, based on an example that might be a bit easier for you to understand.
Let's imagine we're not talking about objects, but *tables*. When you say something like "A table has four legs", you're not talking about a *specific* table, but rather, you're talking about the *concept* of what a table is. In Java, this is what a class is. Now, when you say something like "This table has four legs" while pointing to the table on the other side of the room, then you're talking about a very specific table, not just a concept anymore. In Java, this is what an object is. When we now say "object oriented", we merely mean "code that makes use of objects."
Another way to think of it is that classes are *blueprints* for certain things, and objects are the actual constructions that were created by following those blueprints.
## Creating Objects
So let's stick with this table example for now. First, we'll create a class called `Table` like so:
```java
public class Table {
}
```
So now, we have a very basic blueprint of a table.
Now, let's go back into our main class and actually create a *specific* table like so:
```java
public class Main {
public static void main(String[] args) {
// This is our usual main method
Table myTable;
myTable = new Table();
}
}
```
As you can see, the structure of line 5 is the same as how we usually declare variables: `Table` is the variable type (this is where we usually put `int` or `String`), and `myTable` is the name of the variable. Line 6 is where the new stuff happens, however: We set the variable's *value* to a newly created *instance* of the `Table` class. This is us creating a specific table following the blueprint that is the `Table` class: We write `new`, then the class' name, and then opening and closing parentheses `()`.
# Fields
What we have so far isn't really that interesting yet - our table doesn't really have any properties yet. But now, we can actually add *fields* to our class: Certain variables that each object will share, but whose values can also be *different* for each object. Fields are declared like normal variables, but are placed at the top of a class, prefixed with the `public` keyword like so:
```java
public class Table {
public int legAmount;
}
```
Now, each table *instance* can have its amount of legs modified as well as displayed like so:
```java
Table myTable = new Table();
myTable.legAmount = 4;
System.out.println("My table has " + myTable.legAmount + " legs");
```
As you can see, to access an object's fields, all you have to do is write the variable name, followed by a dot `.`, followed by the field's name.
The important thing to know here is that this variable is now unique for *every table instance you create*, meaning the following code...
```java
Table myTable = new Table();
Table myOtherTable = new Table();
myTable.legAmount = 4;
myOtherTable.legAmount = 25;
System.out.println("My table has " + myTable.legAmount + " legs");
System.out.println("My other table has " + myOtherTable.legAmount + " legs");
```
...will print out `My table has 4 legs`, followed by `My other table has 25 legs`.
# Methods
Obviously, you already know what methods are, but we can finally get rid of the `static` keyword for those as well. Similarly to what we can do with fields, we can also add methods to classes that can do unique things based on the object they're being called for. Let's take a look at the following code:
```java
public class Table {
public int legAmount;
public void printLegAmount() {
System.out.println("I have a certain amount of legs");
}
}
```
As you can see, I've added a (non-static) method to this class called `printLegAmount`. Now we can change the code in our `Main` class to just call that method instead:
```java
Table myTable = new Table();
myTable.legAmount = 4;
myTable.printLegAmount();
```
## The `this` keyword
Now that specific example isn't really that useful yet, because every single table we create will print out `I have a certain amount of legs`, when it would really be nicer if the table printed out its amount of legs stored in the `legAmount` variable.
That's exactly what the `this` keyword is for.[^3] Let's modify our method from before:
```java
public class Table {
public int legAmount;
public void printLegAmount() {
System.out.println("I have " + this.legAmount + " legs");
}
}
```
As you can see, `this` is being used here similarly to how we were using `myTable.legAmount` earlier. That has a good reason: `this` also refers to an object. However, the object that `this` refers to is *the current one*. When calling a method like `printLegAmount` for a specific object (`myTable` in our case), the `this` keyword will always refer to that object.
So if we have code like this...
```java
Table myTable = new Table();
Table myOtherTable = new Table();
myTable.legAmount = 4;
myOtherTable.legAmount = 25;
myTable.printLegAmount();
myOtherTable.printLegAmount();
```
...then the result will be the same as before: `My table has 4 legs`, followed by `My other table has 25 legs`, because in `myTable.printLegAmount()`, `this` will be `myTable`, and in `myOtherTable.printLegAmount()`, `this` will be `myOtherTable`.
# Constructors
Constructors are a special kind of method in Java. You can't really execute them manually, but instead, they're automatically called every time a new instance is created (so every time you use the `new` keyword).
Let's look at the following example, which adds a constructor to our `Table` class that automatically sets the `legAmount` variable to 4.
```java
public class Table {
public int legAmount;
// The constructor
public Table() {
this.legAmount = 4;
}
public void printLegAmount() {
System.out.println("I have " + this.legAmount + " legs");
}
}
```
As you can see, a constructor is different from other methods in that it *doesn't have a name*, and that its return type is *the same as the class it is in*.
So now, any instance of `Table` that we create will automatically have a leg amount of 4:
```java
Table myTable = new Table();
myTable.printLegAmount(); // "I have 4 legs"
```
## Constructor parameters
Just like in any other method, we can add a set of variables that the constructor accepts (parameters). In our case, we could make a table request a `legAmount` when it's initialized as follows:
```java
// The constructor
public Table(int legs) {
this.legAmount = legs;
}
```
Now, when creating a new instance of `Table`, we have to actually give it a leg amount that it should start with:
```java
Table myTable = new Table(4);
```
# So what about `static`?
As you have learned throughout this tutorial, non-static methods and variables inside of classes have a key property: They're unique for every object that is created of that class. So why have we been using `static` throughout both our `Main` and our `Utility` classes?
The `static` keyword tells any field or method that it should *not* be unique for every object that is created. Even if we created an instance of our `Main` class (`new Main()`), for example, the `main` method would not be unique for that instance, because it is marked with the `static` keyword. This also means that using the `this` keyword would make no sense in a static method (which object would it reference?), which is why doing so doesn't work.
Similarly, if we made the `abs` method in our `Utility` class non-static, then we could *not* access it simply by typing `Utility.abs()`, because the method would be unique for every object that is created of the class. We *could* then use the `abs` method if we first created an instance of the `Utility` class (`new Utility()`).
# Conclusion
So yea, that's quite a lot of information for you to process. The introduction to objects is a pretty big step, but it also allows for a lot of cool new stuff that you can learn. In the next couple tutorials, we'll expand on the concept of objects and learn some more stuff that you can do with them to make them even more useful.
For now, though, I'll leave you with this little exercise you can do to facilitate what you learned about objects and to get a better understanding of how useful they are:[^4]
> Let's imagine you're managing a small car dealership. You want to have a way of managing all of the cars you have in stock. This includes keeping track of their brand names, their horsepower, their license plate texts and their mileage. Currently, you have four different cars in stock, and you want your program to print out all of the information about all of the cars.
If you're stuck, you can [get some hints](https://gist.github.com/Ellpeck/462022597659f554fdd75663359480d3) or [look at my solution](https://gist.github.com/Ellpeck/7a0f31306d05473c10e8bca1685510a4).
Next time, we'll be talking about arrays and lists.[^5] Happy coding!
***
[^1]: Java's default `Math` class already has an `abs` method that does this same thing, but for the sake of this tutorial, we'll ignore it (because I can't think of any other utility method to add, in all honesty).
[^2]: Note that I explain why we're omitting the `static` keyword from now on a bit later in this tutorial.
[^3]: In a lot of cases, using the `this` keyword is actually optional and it can just be left out altogether. However, I personally think that it increases code readability quite a bit and that it helps a lot with understanding how objects work, so I advise you to use it.
[^4]: This example is one that almost every instructor uses. It might be a bit boring and ridiculous, but it's simple and helps understanding the concept, so I also like using it when teaching people how to code.
[^5]: Note that there is also another huge topic to talk about when it comes to object orientation, which is *pointers* and how Java manages objects and their assignment to variables (including pass-by-reference and the like). I'll cover that soon. I promise. <3

166
blog/java_5.md Normal file
View file

@ -0,0 +1,166 @@
After that complicated stuff we did in the last tutorial, how about we take it down a notch in this one and talk about some additional things that I haven't mentioned this far, but that will still be very useful to you as a programmer. Most of these things won't really have much connection to each other, but I'll give you an example at the end of this tutorial that combines some of them into a single use case.
# Shorthands
Java has some shorthands to make addition and other mathematical operations faster. As there isn't really that much to explain (they're really just vocabulary), here's a list of them:
```java
// These two statements are equivalent
// (This also works with all other math operators)
i = i + 2;
i += 2;
// When adding or subtracting 1, you can just do this:
i++;
i--;
```
# Primitive Types
Primitive types (also called *native types*) are variable types in a language that are baked into the very foundation of the language. That means they don't derive from a class, but they just exist as part of the language itself. The two primitive types you already know are `int` (32 bit integers) and `boolean` (`true` and `false`). In Java, primitive types always start with a lowercase letter (instead of an uppercase letter for classes).
Along with `int` and `boolean`, there are some more primitive types that could come in useful when programming. Here are the ones I haven't mentioned yet, quickly summed up:
```java
// "char" represents a single character.
// Characters use single quotes ' rather than double quotes " (which are reserved for strings).
char character = 'A';
char fourthChar = "Hello World".charAt(4); // o
// "double" represents a 64bit floating point number
// which is a number with a decimal point
// It's called a double because it has double the precision of float (see below)
double d = 3.14;
// "float" represents a 32bit floating point number (less precise than double).
// To differentiate floats from doubles and ints, you have to append an "F".
float f = 12.5F;
// "long" represents a 64bit integer (which can store a lot higher numbers than int)
// To differentiate longs from ints, you have to append an "L".
long l = 9223372036854775807L;
// "short" represents a 16bit integer
short s = 32767;
// "byte" represents an 8bit integer
byte b = 127;
```
## What about `String`?
Oh boy. Strings are quite a mess in Java, because they *behave* like primitive types (more on that later), but they really *are* classes. Also, you can just initialize a string using quotes `""` instead of having to call `new String()`, which makes them behave differently than *any other class* in the language. This complication is also the reason that you can't just compare two strings using `==`, but you have to use `.equals()` instead. Meh.
## Pass-by-reference vs. pass-by-value
An important thing to know about primitive types compared to objects is the behavior they show when being passed into a method that accepts parameters. Let's look at the following code as an example:
```java
public class Main {
public static void main(String[] args) {
int i = 10;
addOne(i);
System.out.println(i); // still 10, what gives?
}
private static void addOne(int i){
i++;
}
}
```
As you can see, the `addOne` method adds one to the parameter passed into it. But despite that, the code in our `main` method still prints out the number 10. This happens because primitive types (and `String`, hurr durr) use what is called *pass-by-value*: When calling `addOne`, its `i` variable is just set to the *value* of the `i` variable from our `main` method. It doesn't know about the actual *variable* we pass in, just its *value*.
This behavior, however, is different when using Objects. Let's rewrite the code above to use a custom class that contains an `int` field:
```java
// Thing.java
public class Thing {
public int i;
public Thing(int i) {
this.i = i;
}
}
// Main.java
public class Main {
public static void main(String[] args) {
Thing thing = new Thing(10);
addOne(thing);
System.out.println(thing.i); // 11
}
private static void addOne(Thing thing) {
thing.i++;
}
}
```
As you can see, the program now prints out 11 instead of 10. That's because of something Java uses called *pointers*[^1]. Our `thing` variable in the `main` method doesn't really store the actual *instance*, but instead, it stores the location of that instance in memory. So when passing that variable into the `addOne` method, all we pass is the information "The thing we're trying to modify sits in this part of memory, so modify the data there, please." That's why, when we then change the `Thing`'s `i` variable, it's also modified outside of the method.
# `null`
Any variables that can store objects rather than primitive types (our `thing` variable from before, for example) can have a state where they don't point to an object, but instead, point to nothing. This "nothing" is called `null`.
```java
Thing noThing = null;
```
Using `null` can be useful in a lot of cases; however, it can also be quite dangerous: Trying to call a method or interact with a field of a variable that is `null` causes the program to crash:
```java
Thing noThing = null;
System.out.println(noThing.i); // crashes
```
Just like any other objects, you can use `null` in comparisons:
```java
if (noThing == null) {
System.out.println("I don't have a thing!");
}
```
# Arrays and Lists
Arrays and Lists are two data types that you'll be using a lot of, because they're super useful. Their names kind of already give away what they do, but in case they don't mean anything to you: Arrays and lists are two ways to store multiple pieces of data easily without having to manage each piece on its own. This can be useful for stuff like *students in a classroom*, where you won't really know for certain beforehand how many students will be there at any given day. The difference between Arrays and Lists is that the former has a *fixed size*, while the latter has a *dynamic size*.
## Arrays
Let's take a look at arrays first. An array has a fixed type, meaning that the objects in it can only be of the type you specify when creating the array. The variable type of an array of strings, for example, is written as `String[]`. To initialize an array, you have two options: Either you create an empty array based on a fixed size (where every slot will contain `null` to start with), or you create an array that already has stuff in it:
```java
int[] numbers = new int[10]; // empty array with 10 slots
String[] names = new String[]{"Justus", "Peter", "Bob"};
```
You can query and modify the data in an array using array brackets `[]` like so:
```java
String justus = names[0]; // get the 0th entry
names[1] = "Peter Shaw"; // modify the 1st entry
```
Note that, in Java, the first slot in an array has an index of 0, which means that the last slot of an array always has the index `length-1`. Every array has a `length` field which stores the amount of slots an array has, meaning you can query the length of `numbers` by writing `numbers.length`.
## Lists
Lists[^2] work much in the same way, with the main difference being the way you identify them. Lists use something called *generic types*[^3] to allow you to also give them a fixed type. The variable type of a list of strings, for example, is written as `ArrayList<String>`, using angle brackets `<>` (which are really just "greater than" and "less than" signs). To create a list, simply call its constructor using `new`:
```java
ArrayList<Integer> numbers = new ArrayList<>();
ArrayList<String> names = new ArrayList<>();
```
Note that, to create a list containing a primitive type, you need to specify its *wrapper class* instead of the native type itself[^4]. The wrapper class for `int` is `Integer`, and the wrapper classes for the other types are just their names with an uppercase first letter (`Boolean`, for example).
Also note that, when copying or writing this code in your IDE, it will automatically add `import` statements to the top of the class. All they do is make classes from other locations available to the current class, but you don't have to worry about them too much.
You can query and modify the data in a list using the methods from the `ArrayList` class:
```java
names.add("Justus"); // add entries
names.add("Peter");
names.add("Bob");
String justus = names.get(0); // get the 0th entry
names.set(1, "Peter Shaw"); // modify the 1st entry
// the length of a list can be read using the size() method
int length = names.size();
```
Note that, when trying to access an index of a list or an array that is either less than 0 or greater than or equal to its length, your program will crash, so be careful.
# Conclusion
So today, you learned some additional useful things about Java that I didn't really get a chance to mention at an earlier point. So let's write some code that makes use of them! If you want, you can try to solve the following problem, which is an extension of the problem from the previous tutorial:
> Let's imagine you're managing a small car dealership. You want to have a way of managing all of the cars you have in stock. This includes keeping track of their brand names, their horsepower, their license plate texts and their mileage. Currently, you have 15 parking spots for cars you can sell, 4 of which are already occupied with cars. Additionally, you keep track of a list of all the customers you have had so far (namely, you store their first and last names as well as the city they live in). At the current time, you've already had 3 customers. Additionally, you want to have a way of adding a new car to the first available parking spot, as well as a way to store the data of a new customer easily.
If you're stuck, you can [get some hints](https://gist.github.com/Ellpeck/3bdc69845d0d4c0e9511511dd06cfdc1) or [look at my solution](https://gist.github.com/Ellpeck/595b929ce666fca6fb0ed792ca98d71e).
Happy coding!
***
[^1]: Java's pointers work a lot differently from pointers in lower-level languages like C, because they're implicit: You don't create or manage them yourself. They're still called pointers though, so yea.
[^2]: Java has multiple types of lists (including, but not limited to `ArrayList` and `LinkedList`), but for the purpose of this tutorial, we'll only be looking at `ArrayList` since it's the most commonly used one and it also goes hand in hand with arrays.
[^3]: More on those in a later tutorial, probably. They're pretty useful.
[^4]: Which is also just another annoying property of Java that could've been implemented a lot better (like in C#), but oh well, it is what it is.

151
blog/java_6.md Normal file
View file

@ -0,0 +1,151 @@
For this tutorial, let's expand on the car dealership example from the end of the last tutorial: You have a car dealership, and you want to expand it to sell various other types of motor vehicles; let's say... motorbikes and trucks as well. Obviously, these different kinds of vehicles all have different properties: A motorbike requires the use of a helmet, and an important piece of information about a truck might be how big its storage area is.
Now, this is where object-oriented languages like Java shine.
# Extending Other Classes
The concept of extending other classes is probably best explained with an example, so to start out with, let's create a class that represents a car with a certain amount of wheels:
```java
public class Car {
public int amountOfWheels;
public Car(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
}
```
As you can see, this is just a simple class with a variable that can store how many wheels a vehicle has.
Now, let's imagine we want to create a class that represents a truck, and we would want any truck to store information on how many wheels it has, as well as how many cubic meters of storage it has available:
```java
public class Truck {
public int amountOfWheels;
public int storageArea;
public Truck(int amountOfWheels, int storageArea) {
this.amountOfWheels = amountOfWheels;
this.storageArea = storageArea;
}
}
```
As you can see, just creating the class like usual isn't really that pretty, because now both our `Car` class and our `Truck` class have separate `amountOfWheels` fields, despite the fact that they really refer to the same thing: The amount of wheels of a vehicle.
What we'd like to be able to do instead is to have the amount of wheels stored in a different location that both our `Truck` and our `Car` class can access. The first step of achieving this is creating another class that only stores information both of our vehicles should have:
```java
public class Vehicle {
public int amountOfWheels;
public Vehicle(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
}
```
Now, we can use the `extends` keyword that Java provides to cause both of our other classes to also have access to this information. Let's modify our `Car` class first and I'll explain afterwards what exactly our modification will do.
```java
public class Car extends Vehicle {
public Car(int amountOfWheels) {
// Note that the line below will be explained in a bit
super(amountOfWheels);
}
}
```
As you can see, our `Car` class now `extends Vehicle`. This means that the `Car` class is basically just an *upgraded* version of the `Vehicle` class: It contains all of the information that the `Vehicle` class provides (including fields and methods), but it can also provide its own additional information. From the perspective of `Car`, `Vehicle` is the *superclass*, and from the perspective of `Vehicle`, `Car` is a *child class*.
It should be noted that any class can only extend *one* other class, so `Car` couldn't extend both `Vehicle` and, let's say, `FueledDevice`. However, the other way around is possible: `Vehicle` can be extended by multiple classes, meaning that we can modify our `Truck` class to also be a child class like this:
```java
public class Truck extends Vehicle {
public int storageArea;
public Truck(int amountOfWheels, int storageArea) {
super(amountOfWheels);
this.storageArea = storageArea;
}
}
```
As you can see, the `Truck` class now uses the `amountOfWheels` field from its superclass, but it defines its own `storageArea` field that only trucks have.
## `super`
Now, you've probably been wondering what line 5 means: `super(amountOfWheels)`. As we defined earlier, our `Vehicle` class takes one argument in its constructor: The amount of wheels that it has. As we also previously discussed, a constructor *has to be called* when a new object is created. So, when creating a `Truck` object, the `Truck` constructor is called, but, in turn, it needs to also implicitly call the `Vehicle` constructor.
When placing a `super()` call at the start of the constructor of a class, the constructor of the class that is being extended will be called. If that class takes a set of arguments in its constructor, we have to pass it those arguments, similarly to if we were creating an object of that class.
That's what our `super(amountOfWheels)` is doing here: It's calling the constructor of `Vehicle` with the `amountOfWheels` argument.
It should be noted that we don't *necessarily* have to pass a variable into the super constructor. For instance, we know for a fact that a truck will always have four wheels, so we can modify our `Truck` constructor to always specify 4 as the wheel amount given to the superclass:
```java
public Truck(int storageArea) {
super(4);
this.storageArea = storageArea;
}
```
## The `Object` class
It should be noted at this point that *all classes* extend the `Object` class implicitly (meaning you never have to write `extends Object` anywhere and it will still hold true). This will be useful pretty soon.
## About Variable Types
Let's imagine we want to store the stock that we sell at our dealership, without specifying separate lists for the different kinds of vehicles we're selling. What we can do is have that variable be of type `Vehicle`, which will automatically allow instances of child classes to be stored in the variable (or list) like so:
```java
ArrayList<Vehicle> stock = new ArrayList<>();
stock.add(new Car(4));
stock.add(new Truck(10));
// or in the case of variables
Vehicle car = new Car(4);
Vehicle truck = new Truck(10);
```
# `instanceof` and Type Casting
Staying with our list of the stock we have, let's imagine we want to find all of the *trucks* that we have in stock, disregarding any other kinds of vehicles that we might have.
This is what the `instanceof` keyword can be used for: Using it, you can ask any object if it derives of a certain class; that is, if any object is an instance of the specified class.
Let's create a method to return a list of all of the cars we have in stock:
```java
import java.util.ArrayList;
public class Main {
private static ArrayList<Vehicle> stock = new ArrayList<>();
public static void main(String[] args) {
stock.add(new Car(4));
stock.add(new Truck(10));
getTrucks();
}
private static void getTrucks() {
for (int i = 0; i < stock.size(); i++) {
Vehicle vehicle = stock.get(i);
if (vehicle instanceof Truck) {
System.out.println("Vehicle " + i + " is a truck!");
}
}
}
}
```
As you can see, I've expanded the code from before to have a `getTrucks` method, which iterates through every vehicle we have in stock and if it finds a vehicle that is an instance of the `Truck` class, it will print out a message.
Another way to think about the `instanceof` keyword is as a weird, clunky synonym to the word "is": When asking `vehicle instanceof Truck`, we're really asking "Is this vehicle a truck?"
Now that we know which of the vehicles we have in stock are trucks, it might also be useful to know the storage capabilities of all of those trucks. However, as you can see, the `i`th vehicle we're currently inspecting is always of typ `Vehicle`, as that's what we store in the `stock` list. That means that we won't be able to access any `Truck`'s `storageArea` variable. That's what the concept of *type casting* can be used for.
If you have a variable of a certain type, but you know for a fact that it's actually an instance of a *child class* of that type, then you can change that variable to be of the child class' type instead like so:
```java
Vehicle vehicle = stock.get(i);
if (vehicle instanceof Truck) {
Truck truck = (Truck) vehicle;
System.out.println("The " + i + "th truck can store " + truck.storageArea);
}
```
As you can see, to type cast a variable into a different type, all you have to do is write that type in parentheses `()` in front of the variable.[^1] Cool.
# Conclusion
So today, you learned one of the important aspects of object oriented programming, which is very useful in a heap of different scenarios, as you will be able to see throughout the next tutorials. Next time, we'll be covering overriding methods from superclasses, which will allow you to do even more cool stuff with inheritance.[^2]
I'm sorry that I've been taking such a long break from the tutorials, but after asking on Twitter if you people actually learn from them, a lot of you said that you do, and so that gave me the motivation to continue working on these.
So thanks a lot, and happy coding! <3
***
[^1]: As you might be able to see now, this concept is called type *casting* because it's like we're putting the vehicle into a mold (or a *cast*) to reshape it into a different type.
[^2]: I was originally planning on including that in this tutorial already, but I noticed that that might be a bit too much after all, so I'll be doing it next time instead.

227
blog/java_7.md Normal file
View file

@ -0,0 +1,227 @@
So it's been a hot minute since the last tutorial, and I apologize for that. However, it seems like there are some people that actually use these tutorials to properly learn Java, and so I didn't want to leave you all hanging.
Today's tutorial is going to cover method overrides, which are another awesome object orientation concept that will help you out greatly when programming.
Let's imagine that we want our different vehicle types (`Car` and `Truck`) from the last tutorial to be able to print out some information about themselves to the console. To do so, we could simply add a `printInformation()` method to each of the classes. However, that would end up being problematic if we wanted to print out information about *the entirety of our stock*, which, as you might recall, is stored in our `ArrayList<Vehicle> stock`, as we would have to create `instanceof` checks for both `Car` and `Truck` to be able to access their `printInformation()` methods.
An easy fix for that would be to create a basic `printInformation()` method in our base class (`Vehicle`) and then *override* that method in our subclasses, allowing us to change their behavior. Let's see what that would look like:
```java
// Vehicle.java
public class Vehicle {
public int amountOfWheels;
public Vehicle(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
public void printInformation() {
// Does nothing for now
}
}
// Car.java
public class Car extends Vehicle {
public Car(int amountOfWheels) {
super(amountOfWheels);
}
@Override
public void printInformation() {
System.out.println("This car has " + this.amountOfWheels + " wheels");
}
}
```
As you can see, I've added a simple `printInformation()` method to the `Vehicle` class (which does absolutely nothing for now). However, I have then *overridden* that method in the `Car` class. The way I've done that is by adding a method that has the exact same name, return type and accepted parameters as the base class's method. Technically, that would already be enough to override a method, however, to make it a little clearer to read, people usually like to add the `@Override` annotation[^1] above the method.
So what does this mean, exactly? Basically, when calling the `printInformation()` method on an instance of the `Vehicle` class, nothing will happen, because the `Vehicle`'s method is empty. However, when calling the method on an instance of the `Car` class, the print statement above will be executed, printing information about the car's wheel amount.
The important thing to understand is this: Which class's method is called isn't determined by the *variable type*, but by the type that the object itself has. Let's check out this example:
```java
// This is in our Main class from last time
private static void getTrucks() {
for (int i = 0; i < stock.size(); i++) {
Vehicle vehicle = stock.get(i);
vehicle.printInformation();
}
}
```
Now, each vehicle that isn't a car (so each truck in ou example) will not print out any information, but each of our cars will print out the information we specified above, despite the fact that we're not doing any `instanceof` checks or anything else. Cool.
# Calling `super` methods
Now, if we wanted to also add an override of the `printInformation()` method to our `Truck` class, we will come across a minor annoyance: To print out the truck's amount of wheels, we'd basically have to copy the print statement from our `Car` class, which is a bit ugly.
Well, that's where `super` calls come to the rescue! We've already briefly touched on super calls when talking about the super constructor for extending other classes, and super calls are very similar to that. Let's change our code up a bit, and then I'll explain what it all means.
```java
// Vehicle.java
public class Vehicle {
public int amountOfWheels;
public Vehicle(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
public void printInformation() {
System.out.println("Vehicle info:");
System.out.println("Wheel amount: " + this.amountOfWheels);
}
}
// Car.java
public class Car extends Vehicle {
public Car(int amountOfWheels) {
super(amountOfWheels);
}
@Override
public void printInformation() {
super.printInformation();
}
}
```
As you can see, I've modified the `Vehicle`'s method to print out the wheel amount by default, and I've changed the `Car`'s method to call `super.printInformation()`. What this call does is simply execute its parent class's `printInformation()` method, so a car will also have its wheel amount printed out just the same as any other vehicle.
It should be noted that simply calling the `super` method in an override and doing absolutely nothing else is *the default behavior*, meaning that, in the example above, we could leave out the `printInformation()` override in our `Car` class completely and still get the same effect.
Now let's add a `printInformation()` override to our `Truck` class as well, but this time, let's extend the behavior a bit:
```java
public class Truck extends Vehicle {
public int storageArea;
public Truck(int storageArea) {
super(4);
this.storageArea = storageArea;
}
@Override
public void printInformation() {
super.printInformation();
System.out.println("Storage area: " + this.storageArea);
}
}
```
Now, calling a truck's `printInformation()` method will *first* print out "Vehicle information:", then the wheel amount, and then the storage area. If we wanted this behavior to occur in a different order, we could simply swap the two lines as follows:
```java
@Override
public void printInformation() {
System.out.println("Storage area: " + this.storageArea);
super.printInformation();
}
```
Now, the storage area will be printed out *first*, followed by "Vehicle information:" and the wheel amount.[^2]
# `Object` methods
As I briefly mentioned in the last tutorial, *all* classes implicitly extend Java's `Object` class without you having to specify that information. This is finally going to be useful now, as this class provides some useful methods that we can override in our implementations.
## `toString()`
This method is probably the most versatile one of the bunch: It gets called whenever an object's information needs to be converted into a string in some circumstance. For example, if you simply write
```java
Car car = new Car(4);
System.out.println(car);
```
then the car's `toString()` method will be called inside of `println` in order to convert the car's information into a string.
However, by default, the `toString()` method simply prints out some not-so-useful information about the object's internal identifier. If we override this method, however, we can make it display some more useful information. Let's do so in our `Vehicle` and `Car` class as an example:
```java
// Vehicle.java
public class Vehicle {
public int amountOfWheels;
public Vehicle(int amountOfWheels) {
this.amountOfWheels = amountOfWheels;
}
@Override
public String toString() {
return "Vehicle with " + this.amountOfWheels + " wheels";
}
}
// Truck.java
public class Truck extends Vehicle {
public int storageArea;
public Truck(int storageArea) {
super(4);
this.storageArea = storageArea;
}
@Override
public String toString() {
String vehicleInfo = super.toString();
return vehicleInfo + " and " + this.storageArea + " storage area";
}
}
```
As you can see, `Truck` additionally calls the `toString()` super method and then appends some more information to the string created in `Vehicle`. Pretty nice.
## `equals()`
Remember how I told you that you should always use `equals()` to compare two strings instead of the double equals sign `==`?
Well, here's why: Java's `String` class overrides the `equals()` method from `Object` and changes its behavior so that two strings are considered equal if their contents are identical.
As an example, let's first override the `equals()` method in our `Vehicle` class with the default behavior that it would have if you didn't override it at all:
```java
@Override
public boolean equals(Object other) {
return this == other;
}
```
As you can see, the default behavior is simply the double equals sign `==`, which compares if two variable pointers point to the exact same object, as previously explained. That's not what we might want in our example, though.
Let's expand our `Vehicle` class to also have a unique identifier: The license plate's text. Let's say that we want to identify each vehicle by its plate, and so we make it the key factor in determining whether two vehicles are the same or not:
```java
public class Vehicle {
public int amountOfWheels;
public String licensePlate;
public Vehicle(int amountOfWheels, String licensePlate) {
this.amountOfWheels = amountOfWheels;
this.licensePlate = licensePlate;
}
@Override
public boolean equals(Object other) {
if (other instanceof Vehicle) {
Vehicle v = (Vehicle) other;
return v.licensePlate.equals(this.licensePlate);
}
return false;
}
}
```
As you can see, each `Vehicle` now accepts a license plate in the constructor, and the `equals()` method is overridden in a way that makes two vehicles be considered equal if their license plates match exactly.
Now, a great example of how this could be useful is with lists. The `ArrayList` class contains the `contains()` method, which determines if a certain element is already present in the list. Now, the cool thing is that this method uses the `equals()` method on each element to determine whether or not an element is present. We can use this behavior to check if a vehicle with a certain license plate is already in our stock pretty easily:
```java
import java.util.ArrayList;
public class Main {
private static ArrayList<Vehicle> stock = new ArrayList<>();
public static void main(String[] args) {
stock.add(new Car(4, "AC JS 1999"));
stock.add(new Truck(10, "AC NS 1998"));
Car carToCheck = new Car(4, "AC HI 1234");
if (stock.contains(carToCheck)) {
System.out.println("The queried car is already in our stock :)");
}
}
}
```
Obviously, in this specific example, the text won't be printed because there isn't a car with that license plate in the `stock` list.
# Conclusion
So yea, today you learned about the second key concept of object orientation in Java. To put this knowledge to the test, I'm going to give you an exercise all about extending classes and overriding methods that you can try to solve if you want.
> Let's imagine you're trying to create a program that allows you to draw manage different shapes, namely rectangles, right angle triangles and circles. Obviously, these shapes all have different properties, like the rectangle's and triangle's side lengths and the circle's radius. All of the shapes you currently have are stored in a list. For each of the shapes, you want to be able to calculate its circumference as well as its area. To test this program, you add a couple of shapes to your list and calculate the average of all of their areas and circumferences.
Note that you can find some methods and constants you might need in Java's default `Math` class, namely `Math.PI` and `Math.sqrt()`, the latter of which is used to calculate a square root. If you're stuck, you can [check my solution](https://gist.github.com/Ellpeck/8cf63c747313e070b7475d99e2bed5a1).
As always, happy coding!
***
[^1]: Annotations are another rather advanced topic that almost never comes up when using Java. They can be useful sometimes, but you'll hardly ever have a use for them.
[^2]: Which, in this case, would obviously be a bit of a nonsensical order to display this information in. But you get the idea.

21
blog/lows.md Normal file
View file

@ -0,0 +1,21 @@
So some of you might be disappointed that this *isn't* the next Java tutorial. Well, today and the last couple of days, I haven't been feeling like working on those, because.. I've been depressed again. I never know how to describe it, or how to say it without seeming like it's just.. something I say when I'm too lazy to work on things.
This post will probably be a little all over the place, because that's also the mood that my head is currently in, but I'll try to explain to you what it feels like to me to have a low low follow a high high, mood-wise.
# Highs
When I say that I'm depressed, it means something different than when I say that I'm suffering from depression. Because, for me, depression isn't a constant state. Before depression hits, I have long periods of highs, long periods of what non-depressed people would probably just call.. normal moods.
I get up in the mornings, I enjoy breakfast, I go to University, I do my daily chores, I sit down at my computer, I code, I play some games, and then I go to bed. Normal days, really.
And if you don't suffer from depression, then you might think "Well, what's so special about that?", and the annoying thing is: That's *exactly* what I also think when I'm experiencing a high. I take it for granted, and I never even stop to think about how great it is that I'm currently feeling normal about my life.
# Lows
And then a low hits. And the longer the high has been going on for, the longer I was happy for, the harder it hits. Right now, I've been feeling low for two days, after a high that lasted almost *a month*. When I'm going through a low phase like now, my day works a lot differently.
I wake up at noon. I stay in bed. I check my phone. Hours pass. I think to myself "What am I doing?" I check my phone. After a while, I force myself to get up; I might even take a shower. I find some cookies in the back of my fridge; those'll be good enough for breakfast, I think to myself. I sit down at my computer. I watch some videos. "What am I doing?" Maybe some more videos. "What am I doing? I don't know what to do." I start Minecraft. "This sounds like fun!" I play for five minutes. I close the game again. "What am I doing?" I watch some videos. I start The Sims 4. I play for half an hour, and then I close the game again. "What am I doing?" I watch some more YouTube videos. I listen to some music. Should I do my coursework now? Not in the mood. I open IntelliJ. Let's work on some code. I glance at a couple of files, and then I close it again. "What am I doing?"
Take that whole description, and slow it down by about 200%. That's what a low day feels like to me. I feel like every single hour; every single *minute* even, is filled with doubt and emptiness and just... nothing.
This is so hard to explain for me because I also don't.. see myself in these low phases. I don't look at myself. I don't observe what I'm doing. It's like I'm not even there; it's like I'm not inside my own body, doing these activities with it. It's like it's just doing everything on some sort of weird auto-pilot mode.
So until that passes, I probably won't be working on any tutorials, or anything, really. Let's hope it passes fairly soon, because I'm honestly sick of forcing myself to eat cookies for breakfast. It might sound great, but eating something when you have absolutely no desire to eat things is... quite unpleasant, really.

View file

@ -46,5 +46,61 @@
"id": "rock_bottom_mod",
"date": "10/3/2019",
"discuss": "https://twitter.com/Ellpeck/status/1180092634410487808"
},
{
"name": "Java Tutorial, Part 1: Hello World",
"summary": "The first part of my post series for programming beginners where I explain how to write code in Java.",
"id": "java_1",
"date": "10/10/2019",
"discuss": "https://twitter.com/Ellpeck/status/1182080078827737088"
},
{
"name": "Java Tutorial, Part 2: Intro to Conditions and Loops",
"summary": "The second part of my post series for programming beginners. This one is all about conditions and loops.",
"id": "java_2",
"date": "10/10/2019",
"discuss": "https://twitter.com/Ellpeck/status/1182354544707198976"
},
{
"name": "Java Tutorial, Part 3: (Static) Methods",
"summary": "In this Java tutorial for beginners, we cover what (static) methods, parameters and return types are.",
"id": "java_3",
"date": "10/11/2019",
"discuss": "https://twitter.com/Ellpeck/status/1182775985885847558"
},
{
"name": "Java Tutorial, Part 4: Classes and Objects",
"summary": "In this Java tutorial for beginners, we cover the basics of creating classes with a constructor, some fields and some methods and creating objects of them.",
"id": "java_4",
"date": "10/14/2019",
"discuss": "https://twitter.com/Ellpeck/status/1183857460660101133"
},
{
"name": "Java Tutorial, Part 5: Things I Left Out So Far",
"summary": "In this Java tutorial for beginners, we cover some shorthands, some more data types, the difference between pass-by-reference and pass-by-value, null, as well as arrays and lists.",
"id": "java_5",
"date": "10/17/2019",
"discuss": "https://twitter.com/Ellpeck/status/1184894859133509632"
},
{
"name": "Lows",
"summary": "About depression and what it feels like when I don't know what to do with myself",
"id": "lows",
"date": "10/20/2019",
"discuss": "https://twitter.com/Ellpeck/status/1186028260838334471"
},
{
"name": "Java Tutorial, Part 6: Inheritance",
"summary": "In this Java tutorial for beginners, we cover classes extending other classes and the instanceof keyword.",
"id": "java_6",
"date": "10/31/2019",
"discuss": "https://twitter.com/Ellpeck/status/1189904487722487809"
},
{
"name": "Java Tutorial, Part 7: Overriding Methods",
"summary": "In this Java tutorial for beginners, we cover overriding methods, calling superclass methods and toString().",
"id": "java_7",
"date": "11/26/2019",
"discuss": "https://twitter.com/Ellpeck/status/1199339701640945664"
}
]

BIN
foefrenzy/docs/map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

214
foefrenzy/docs/tutorial.md Normal file
View file

@ -0,0 +1,214 @@
# Creating Custom Maps
Foe Frenzy has a simple system in place that allows you to create your own maps using a simple image editing software as well as a text editor. To play a map, all you have to do is put it into a special folder and the game will automatically load it up. If you want to play a custom map in Multiplayer, you need to make sure that all players have the map installed.
## Where to Put Map Files
All files that the game uses are stored in `Documents/Foe Frenzy` on Windows or `~/Foe Frenzy` on Linux. After launching the game for the first time, a `Maps` folder and a `SpawnPools` folder will be generated. This is the location where custom maps live.
## Creating a Map
When creating a Map, two files are required:
- `Maps/MapName.xml`: This is a file containing information about the map, like which tile is which, where items can spawn, where players can spawn and so on.
Note that the example file included has some more comments inside of it that explain what the different parts of the file do.
- `Maps/Textures/MapName.png`: This file represents the actual layout of the map. You can edit it in an image editor of your choice. Each pixel represents one tile on the map, and the colors of the pixels represent what tiles should be placed there. The size of the image is the same as the size of the finished map.
Note that, further down on this page, there is some information about which colors the game's default tiles have in the map image.
Below is the file as well as the image for an example map with some comments that explain the structure in greater detail. To get started making your own map, it would be easiest to copy this information.
```xml
<?xml version="1.0"?>
<!-- XML header information, don't change this -->
<RawMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!-- A short description of the map that displays when it's selected to be played -->
<Description>A test custom map</Description>
<!-- The amount of seconds it takes until an item spawner spawns an item -->
<ItemSpawnerDelay>5</ItemSpawnerDelay>
<!-- The weather effect that is displayed on the map. Possible values are Snow and Rain -->
<WeatherEffect>Snow</WeatherEffect>
<!-- The type of music that should be displayed on the map. Scroll down for reference on all types -->
<MusicTheme>Lava</MusicTheme>
<!-- The map's global light modifier. This is the amount of "sunlight" that the map has, where 255 for each R, G, B value means "full light" and 0 means "no light". -->
<LightModifier>
<B>255</B>
<G>255</G>
<R>255</R>
<A>255</A>
</LightModifier>
<!-- The intensity of lights (like torches) on the map, 1 means full intensity, 0 means that they give off no light at all -->
<LightIntensity>0</LightIntensity>
<!-- The tile that is placed outside the borders of the map for visual purposes if the map is smaller than the screen size -->
<FillerTile>Grass</FillerTile>
<!-- The structure that is placed on top of the filler tile -->
<FillerStructure>Tree</FillerStructure>
<!-- Tile properties are custom combinations of tiles and structures that can be placed in specific locations using the map image. If multiple tile properties are required, an entire RawTileProperty can be copied and pasted below any number of times. -->
<TileProperties>
<RawTileProperty>
<!-- The color specified in the map image file to make this specific property occur in the map -->
<Color>
<B>151</B>
<G>255</G>
<R>151</R>
<A>255</A>
</Color>
<Tile>Grass</Tile>
<Structure>Vine</Structure>
</RawTileProperty>
</TileProperties>
<!-- This is the list of item spawners, their locations and the spawn pools they spawn from. Scroll down for reference on spawn pools. -->
<ItemSpawners>
<RawItemSpawner>
<Location>
<X>11</X>
<Y>10</Y>
</Location>
<Pool>Test</Pool>
</RawItemSpawner>
<RawItemSpawner>
<Location>
<X>25</X>
<Y>10</Y>
</Location>
<Pool>Test</Pool>
</RawItemSpawner>
</ItemSpawners>
<!-- This is the list of structure spawners. Structure spawners are basically instructions on how to randomly spawn a set of structures onto different tiles of the map. -->
<StructureSpawners>
<!-- The first spawner in this list randomly spawns Rock structures on Sand tiles. The Amount field determines how many tries the spawning should have. -->
<RawStructureSpawner>
<ValidPositions>
<string>Sand</string>
</ValidPositions>
<Structure>Rock</Structure>
<Amount>40</Amount>
</RawStructureSpawner>
<RawStructureSpawner>
<ValidPositions>
<string>Grass</string>
</ValidPositions>
<Structure>Tree</Structure>
<Amount>60</Amount>
</RawStructureSpawner>
</StructureSpawners>
<!-- These are the spawn points for the four players. When spawning, the spawn points are shuffled so that players don't spawn in the same order every time. -->
<SpawnPoints>
<Point>
<X>1</X>
<Y>8</Y>
</Point>
<Point>
<X>27</X>
<Y>4</Y>
</Point>
<Point>
<X>6</X>
<Y>18</Y>
</Point>
<Point>
<X>25</X>
<Y>15</Y>
</Point>
</SpawnPoints>
</RawMap>
```
![](docs/map.png =100%x*)
## Creating a Spawn Pool
When creating a map, the items that can spawn at any given spawn point are referenced by their Spawn Pool. A spawn pool is basically just a list of items mapped to weights, where a higher weight means that the item will spawn more often, and a lower weight means that the item will spawn less often.
Spawn pools are stored in `SpawnPools/PoolName.xml`. Note that the example file included has some more comments inside of it that explain what the different parts of the file do. Also note that, further down on this page, there is a list of the game's default spawn pools.
Below is the file for an example spawn pool with some comments that explain the structure in greater detail. To get started making your own spawn pool, it would be easiest to copy this information.
```xml
<?xml version="1.0"?>
<!-- XML header information, don't change this -->
<RawPool xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!-- These are the entries that this spawn pool has -->
<Entries>
<!-- Each entry contains the name of an item that should be spawned, as well as the weight that item should have. Scroll down for information on what the weight value means. -->
<RawPoolEntry>
<Item>Arrow</Item>
<Weight>10</Weight>
</RawPoolEntry>
<RawPoolEntry>
<Item>Bomb</Item>
<Weight>15</Weight>
</RawPoolEntry>
<RawPoolEntry>
<Item>Sword</Item>
<Weight>15</Weight>
</RawPoolEntry>
<RawPoolEntry>
<Item>LeatherArmor</Item>
<Weight>10</Weight>
</RawPoolEntry>
<RawPoolEntry>
<Item>RubberHammer</Item>
<Weight>5</Weight>
</RawPoolEntry>
<RawPoolEntry>
<Item>Spear</Item>
<Weight>15</Weight>
</RawPoolEntry>
<RawPoolEntry>
<Item>Potion</Item>
<Weight>3</Weight>
</RawPoolEntry>
</Entries>
</RawPool>
```
## Tile Names And Colors
Each tile color is written as (R, G, B). The alpha value (A) is always 255.
- Rock (255, 255, 255)
- Grass (0, 255, 0)
- Sand (255, 255, 0)
- Water (0, 0, 255)
- DeepWater (0, 0, 96)
- Dirt (127, 51, 0)
- RockWall (0, 0, 0)
- RockWallFront (81, 81, 81)
- Lava (255, 0, 0)
- Snow (208, 219, 221)
- Ice (96, 96, 255)
- FallGrass (156, 98, 43)
## Structure Names
- Rock
- Tree (automatically changes to snow or fall tree based on the tile below)
- Torch
- Cactus
- GrassTuft (automatically generates on grass as decoration)
- Flower (automatically generates on grass as decoration)
- Vine
- JungleTree
## Item Names
- Bomb
- Sword
- Arrow
- Wall
- RubberHammer
- Pickaxe
- Dynamite
- Spear
- Potion
- FlameThrower
- LeatherArmor
- Feather
- SpeedBoots
- Torch
- Shield
- AntiSlipBoots
## Default Spawn Pools
- All (Most of the game's items)
- Low (Items that are considered lower tier)
- Cave (All items except long-range weapons like arrows)
- CaveLow (Cave items that are considered lower tier)
- Lighting (Lighting items like torches)
- Pickaxe (Items to destroy rocks with like pickaxes, bombs and dynamite)
- Ice (Most of the game's items, including flame thrower, anti-slip boots and other ice stuff)
## Music Themes
- Lava
- Beach
- Cold
- Forest
- Desert

BIN
foefrenzy/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

164
foefrenzy/index.html Normal file
View file

@ -0,0 +1,164 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Foe Frenzy</title>
<meta name="author" content="Ellpeck">
<meta name="description" content="A fast-paced fighting game where you battle up to three of your friends with random, short-lasting items">
<meta name="keywords" content="Ellpeck, Foe Frenzy, Steam, Discord, itch, itch.io, Battle, 2d, Top Down, Pixelart, MonoGame, Fighting, Game">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">
<link rel="stylesheet" href="style.css">
<link rel="icon" href="favicon.ico">
<meta property="og:title" content="Foe Frenzy">
<meta property="og:description" content="A fast-paced fighting game where you battle up to three of your friends with random, short-lasting items">
<meta property="og:image" content="https://www.ellpeck.de/foefrenzy/media/logo.png">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@Ellpeck">
<meta name="twitter:creator" content="@Ellpeck">
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-150032076-2"></script>
<script>
window.dataLayer = window.dataLayer || [];
let gtag = function () {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'UA-150032076-2');
</script>
</head>
<body>
<div class="main rounded">
<img src="media/banner.png" class="banner rounded" alt="Foe Frenzy Logo">
<p>Foe Frenzy is a fast-paced fighting game where you battle up to three of your friends with random, short-lasting items in an attempt to be the last survivor.</p>
<h1>Get the Game</h1>
<p>You can buy Foe Frenzy for <strong>$12.99</strong> on any of the following platforms.</p>
<div class="centered">
<a class="btn btn-success store-button" role="button" href="https://store.steampowered.com/app/1194170/">
<img src="media/steam.png" class="store-img" alt="Buy on Steam">
</a>
<a class="btn btn-success store-button" role="button" href="https://discordapp.com/store/skus/606152985181028352/">
<img src="media/discord.png" class="store-img" alt="Buy on Discord">
</a>
<a class="btn btn-success store-button" role="button" href="https://ellpeck.itch.io/foefrenzy">
<img src="media/itch.png" class="store-img" alt="Buy on itch.io">
</a>
</div>
<p><small><em>For the time being, Linux users won't be able to buy the game on Discord, as its store is not supported there.</em></small></p>
<h1>Trailer</h1>
<div class="trailer-div">
<iframe class="trailer rounded" src="https://www.youtube.com/embed/Z7ZeuVBNuf4" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<h1>Screenshots</h1>
<div id="carousel" class="carousel slide" data-ride="carousel" data-interval="3000">
<ol class="carousel-indicators">
<li data-target="#carousel" data-slide-to="0" class="active"></li>
<li data-target="#carousel" data-slide-to="1"></li>
<li data-target="#carousel" data-slide-to="2"></li>
<li data-target="#carousel" data-slide-to="3"></li>
<li data-target="#carousel" data-slide-to="4"></li>
<li data-target="#carousel" data-slide-to="5"></li>
</ol>
<div class="carousel-inner rounded">
<div class="carousel-item active">
<img class="d-block w-100 rounded" src="media/screenshots/first.png" alt="Screenshot of the main menu">
</div>
<div class="carousel-item">
<img class="d-block w-100 rounded" src="media/screenshots/second.png" alt="Screenshot of the map selection menu">
</div>
<div class="carousel-item">
<img class="d-block w-100 rounded" src="media/screenshots/third.png" alt="Screenshot of a cave map featuring two players">
</div>
<div class="carousel-item">
<img class="d-block w-100 rounded" src="media/screenshots/fourth.png" alt="Screenshot of a beach map featuring two players">
</div>
<div class="carousel-item">
<img class="d-block w-100 rounded" src="media/screenshots/fifth.png" alt="Screenshot of the victory screen">
</div>
<div class="carousel-item">
<img class="d-block w-100 rounded" src="media/screenshots/sixth.png" alt="Screenshot of the achievements screen">
</div>
</div>
<a class="carousel-control-prev" href="#carousel" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carousel" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
<h1>Features</h1>
<ul>
<li><strong>Local multiplayer</strong> that allows you to battle up to three of your friends. Play with up to two people on the keyboard or connect additional controllers!</li>
<li><strong>Online multiplayer</strong> to play with your friends either over Steam, Discord or using your IP. If you have a friend over, you can even combine local and online multiplayer and play with both at the same time!<sup><a href="#footnote-1">[1]</a></sup></li>
<li><strong>12 maps to play</strong>, all with different interactable terrain; battle your friends in the water, on slippery ice, in a vine-filled jungle, in a dark cave and more!</li>
<li><strong>15 items to battle with</strong>, from swords to bows to wings that you can use to fly over deadly lava!</li>
<li><strong>14 achievements</strong> that you can try to obtain with your friends, each one unlocking an additional character look you can choose for your player!</li>
<li><strong>An original soundtrack</strong> with a unique music style for each map theme, created by the amazing <a href="https://twitter.com/jamalgreenmusic">Jamal Green</a>!</li>
<li><strong>Regular updates</strong> that will add new items, new maps and more!</li>
</ul>
<p>
<small><a id="footnote-1">[1]:</a> When creating a Multiplayer lobby, the host has the option to invite players using Discord's and Steam's online play features, regardless of which platform the host and the invitees have bought the game on. <em>Sadly, Discord's online play feature is currently only supported on Windows.</em></small>
</p>
<h1>More</h1>
<ul>
<li><a href="/foefrenzy/maps">Creating Custom Maps</a></li>
<li><a href="https://ellpeck.de/discord">Join the Discord</a></li>
<li><a href="https://github.com/Ellpeck/FoeFrenzyIssues/issues">Report an Issue</a></li>
</ul>
<h1>About the Project</h1>
<p>
Foe Frenzy is a game created by <a href="https://ellpeck.de">Ellpeck</a>, a student and indie game developer from Germany. It started as a small project inspired by Mario Kart and similar games, because the short-lasting battle items you can use in those games allow for fast and fun gameplay.
</p>
<p>
Development on the project started early in 2019, back when Foe Frenzy was still considered a small side project. Ellpeck made <a href="https://ellpeck.de/blog-small_projects">a blog post</a> about the process of Foe Frenzy's early creation in which he described how creating small projects is easier. After a while of developing the game, Ellpeck took a break from it due to lack of motivation. When he came back to it about two months later, he decided to turn Foe Frenzy into a bigger project, which included redesigning the art for the game, as well as the plan to turn it into a commercial project. Shortly after the rework, he made <a href="https://ellpeck.de/blog-big_projects">a second blog post</a> about this decision.
</p>
<p>With Foe Frenzy, Ellpeck hopes to have created a game that brings fun and banter to many friend groups' gaming meetups.</p>
<h1>Press</h1>
<p>
If you want to create a video or write an article about Foe Frenzy, you are more than welcome to! If you want to have it featured on this site, you can send an e-mail to <a href="mailto:me@ellpeck.de">me@ellpeck.de</a>.
</p>
<p>
If you're a games journalist or a let's player and you would like to request a key for the game, you can also send an e-mail to the address above. Alternatively, you can request a key on Keymailer:
</p>
<div class="centered">
<iframe height="50px" scrolling="no" src="https://embed.keymailer.co/g/games/90201/request_button/_373e672e71" style="border: none" width="200px"></iframe>
</div>
<h2>Assets</h2>
<p>You're free to use the following images for your video or article about Foe Frenzy. Just right-click any of the images you would like to use and select <code>Save image as...</code> to save them.</p>
<div class="assets">
<img src="media/press/banner720.png" class="asset" alt="The game's banner in 16:9">
<img src="media/press/banner.png" class="asset" alt="The game's banner as a strip">
<img src="media/press/logo.png" class="asset" alt="The game's logo">
<img src="media/press/name.png" class="asset" alt="The game's logo and name">
</div>
<p><small>Note that the gray background on some of the images simply exists to make them stand out from this site's background color. It's not actually part of the images themselves.</small></p>
<p class="footer"><a href="https://github.com/Ellpeck/Web">&copy; 2019 Ellpeck</a> &ndash; <a href="https://ellpeck.de/#impressum">Impressum</a></p>
</body>
</html>

70
foefrenzy/maps.html Normal file
View file

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Foe Frenzy - Custom Maps</title>
<meta name="author" content="Ellpeck">
<meta name="description" content="A fast-paced fighting game where you battle up to three of your friends with random, short-lasting items">
<meta name="keywords" content="Ellpeck, Foe Frenzy, Steam, Discord, itch, itch.io, Battle, 2d, Top Down, Pixelart, MonoGame, Fighting, Game, Custom Map, Map, Creating, Creation">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="../style/prettify.css">
<link rel="icon" href="favicon.ico">
<meta property="og:title" content="Foe Frenzy">
<meta property="og:description" content="A fast-paced fighting game where you battle up to three of your friends with random, short-lasting items">
<meta property="og:image" content="https://www.ellpeck.de/foefrenzy/media/logo.png">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@Ellpeck">
<meta name="twitter:creator" content="@Ellpeck">
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/showdown@1.9.1/dist/showdown.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/prettify.js"></script>
<script src="../node/lib/showdown-prettify.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-150032076-2"></script>
<script>
window.dataLayer = window.dataLayer || [];
let gtag = function () {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'UA-150032076-2');
</script>
</head>
<body>
<div class="main rounded">
<a href="/foefrenzy/"><img src="media/banner.png" class="banner rounded" alt="Foe Frenzy Logo"></a>
<div id="tutorial"></div>
<script>
$.ajax({
dataType: "text",
url: "docs/tutorial.md",
success: function (md) {
var converter = new showdown.Converter({
extensions: ["prettify"]
});
var html = converter.makeHtml(md);
$("#tutorial").html(html);
PR.prettyPrint();
}
});
</script>
<p class="footer"><a href="https://github.com/Ellpeck/Web">&copy; 2019 Ellpeck</a> &ndash; <a href="https://ellpeck.de/#impressum">Impressum</a></p>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

BIN
foefrenzy/media/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
foefrenzy/media/discord.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
foefrenzy/media/itch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
foefrenzy/media/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

BIN
foefrenzy/media/steam.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

103
foefrenzy/style.css Normal file
View file

@ -0,0 +1,103 @@
body {
position: relative;
background-image: url("media/background.png");
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
font-family: Roboto;
}
.main {
width: 50%;
display: block;
margin-top: 50px;
margin-bottom: 50px;
margin-left: auto;
margin-right: auto;
padding: 40px;
background-color: white;
}
.banner {
width: 100%;
height: auto;
margin-bottom: 20px;
}
.centered {
text-align: center;
}
.store-button {
margin: 5px;
}
.store-img {
width: 160px;
height: auto;
}
.btn {
border-radius: 0px;
}
h1 {
margin-top: 20px;
}
.trailer-div {
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%;
}
.trailer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.assets {
text-align: center;
}
.asset {
width: 40%;
height: auto;
margin: 10px;
background-color: #BBBBBB;
}
li {
margin-top: 5px;
}
.footer {
margin-top: 40px;
}
#tutorial {
image-rendering: pixelated;
}
@media (max-width: 1200px) {
.main {
width: 80%;
}
}
@media (max-width: 768px) {
.store-img {
width: 100px;
}
}
@media (max-width: 510px) {
.main {
width: 90%;
padding: 20px;
}
}

View file

@ -27,11 +27,20 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/showdown@1.9.0/dist/showdown.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/prettify.js"></script>
<script src="scripts/showdown-prettify.js"></script>
<script src="scripts/main.js"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-150032076-2"></script>
<script>
window.dataLayer = window.dataLayer || [];
let gtag = function () {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'UA-150032076-2');
</script>
</head>
<body data-spy="scroll" data-target="#navbar">
@ -51,7 +60,7 @@
<!-- Navbar content -->
<div class="collapse navbar-collapse" id="navbar-content">
<div class="navbar-nav mr-auto">
<div class="navbar-nav mr-auto" id="nav-items">
<a class="nav-item nav-link" href="#projects">Projects</a>
<a class="nav-item nav-link" href="#social">Social</a>
<a class="nav-item nav-link" href="#about">About</a>
@ -68,92 +77,94 @@
<!-- Content -->
<div class="container main">
<!-- Home -->
<div class="jumbotron">
<div class="container">
<div class="row">
<div class="col-md-auto">
<img src="res/me.png" class="rounded-circle" width="200" height="200" id="navbar-image">
</div>
<div class="col">
<h1 class="display-4" id="intro-text"></h1>
<script src="scripts/greet.js"></script>
<p class="lead">Welcome to my little website! I'm Ellpeck, a student and programmer from Germany. I do a lot of stuff, actually. My life is pretty busy.</p>
<p>Look around this website to find out more about my projects and other places you can find me!</p>
</div>
</div>
</div>
</div>
<!-- Cookie notification -->
<script src="scripts/cookies.js"></script>
<!-- Projects -->
<a class="anchor" id="projects"></a>
<div class="list-display rounded">
<h1>Projects</h1>
<p>
Here is a list of some of the things that you might know me from. If you want to have a more in-depth look at everything I do, check out some of the sites linked in the <a href="#social">Social</a> section.
</p>
<div id="project-list">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
</div>
<script src="scripts/projects.js"></script>
</div>
<!-- Social -->
<a class="anchor" id="social"></a>
<div class="list-display rounded">
<h1>Social</h1>
<p>
These are other websites where you can find me and the things I do, including the pages where I publish my code and games and where I sometimes stream and upload videos. This list also includes a lot of ways to reach me.
</p>
<div class="row">
<div class="col">
<div id="social-list">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
<div id="main">
<!-- Home -->
<div class="jumbotron">
<div class="container">
<div class="row">
<div class="col-md-auto">
<img src="res/me.png" class="rounded-circle" width="200" height="200" id="navbar-image">
</div>
<div class="col">
<h1 class="display-4" id="intro-text"></h1>
<script src="scripts/greet.js"></script>
<p class="lead">Welcome to my little website! I'm Ellpeck, a student and programmer from Germany. I do a lot of stuff, actually. My life is pretty busy.</p>
<p>Look around this website to find out more about my projects and other places you can find me!</p>
</div>
</div>
Additionally, here are some miscellaneous platforms:
<ul>
<li>My Nintendo Switch friend code is <strong>SW-5281-8834-6801</strong></li>
<li>If you want to play my Mario Maker 2 levels, my ID is <strong>8BH-566-4WF</strong></li>
<li>If you play The Sims 4, you can check out my builds on the gallery by searching for <strong>Ellpeck</strong></li>
</ul>
</div>
<div class="col-md-auto" id="discord-div">
</div>
<!-- Projects -->
<a class="anchor" id="projects"></a>
<div class="list-display rounded">
<h1>Projects</h1>
<p>
Here is a list of some of the things that you might know me from. If you want to have a more in-depth look at everything I do, check out some of the sites linked in the <a href="#social">Social</a> section.
</p>
<div id="project-list">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
</div>
<script src="scripts/projects.js"></script>
</div>
<script src="scripts/social.js"></script>
</div>
<!-- Social -->
<a class="anchor" id="social"></a>
<div class="list-display rounded">
<h1>Social</h1>
<p>
These are other websites where you can find me and the things I do, including the pages where I publish my code and games and where I sometimes stream and upload videos. This list also includes a lot of ways to reach me.
</p>
<div class="row">
<div class="col">
<div id="social-list">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
</div>
<!-- About -->
<a class="anchor" id="about"></a>
<div class="list-display rounded">
<h1>About</h1>
<p>
Sometimes, some people ask me some questions about myself or my projects, so I decided to compile a list of some of the answers in a Q&A-like fashion so that I don't have to keep repeating them. If you're curious about me, this might be interesting to you!
</p>
<div id="about-list">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
Additionally, here are some miscellaneous platforms:
<ul>
<li>My Nintendo Switch friend code is <strong>SW-5281-8834-6801</strong></li>
<li>If you want to play my Mario Maker 2 levels, my ID is <strong>8BH-566-4WF</strong></li>
<li>If you play The Sims 4, you can check out my builds on the gallery by searching for <strong>Ellpeck</strong></li>
</ul>
</div>
<div class="col-md-auto" id="discord-div">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
</div>
</div>
<script src="scripts/social.js"></script>
</div>
<script src="scripts/about.js"></script>
</div>
<!-- Blog -->
<a class="anchor" id="blog"></a>
<div class="list-display rounded">
<h1>Blog</h1>
<p>
Occasionally I enjoy writing stuff. So here's some of the stuff I've written. Just click on any of the headers to expand the post. Alternatively, you can subscribe to this blog using <a href="/rss.xml">RSS</a>, <a href="/atom.xml">Atom</a> or <a href="/feed.json">JSON</a>.
</p>
<div id="blog-list">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
<!-- About -->
<a class="anchor" id="about"></a>
<div class="list-display rounded">
<h1>About</h1>
<p>
Sometimes, some people ask me some questions about myself or my projects, so I decided to compile a list of some of the answers in a Q&A-like fashion so that I don't have to keep repeating them. If you're curious about me, this might be interesting to you!
</p>
<div id="about-list">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
</div>
<script src="scripts/about.js"></script>
</div>
<!-- Blog -->
<a class="anchor" id="blog"></a>
<div class="list-display rounded">
<h1>Blog</h1>
<p>
Occasionally I enjoy writing stuff. So here's some of the stuff I've written. Just click on any of the headers to open the post. Alternatively, you can subscribe to this blog using <a href="/rss.xml">RSS</a>, <a href="/atom.xml">Atom</a> or <a href="/feed.json">JSON</a>.
</p>
<div id="blog-list">
<em>The content that should be here is dynamically generated. Please enable JavaScript if you see this.</em>
</div>
<script src="scripts/blog.js"></script>
</div>
<script src="scripts/blog.js"></script>
</div>
</div>

69
node/blog.js Normal file
View file

@ -0,0 +1,69 @@
const {
JSDOM
} = require("jsdom");
const fs = require("fs");
const converter = require("./showdown")(2);
module.exports = function () {
let folder = __dirname + "/../";
createBlogPages(folder);
fs.watchFile(folder + "blog/posts.json", function (curr, prev) {
if (curr.mtime == prev.mtime)
return;
createBlogPages(folder);
});
}
function createBlogPages(folder) {
console.log("Refreshing blog sub-sites...");
fs.readFile(folder + "index.html", function (_, data) {
// set up the template
let templateDom = new JSDOM(data);
var templateDoc = templateDom.window.document;
templateDoc.getElementById("main").innerHTML = "";
let template = templateDom.serialize();
fs.readFile(folder + "blog/posts.json", function (_, data) {
let json = JSON.parse(data);
for (let i = 0; i < json.length; i++) {
let post = json[i];
fs.readFile(folder + "blog/" + post.id + ".md", function (_, content) {
let dom = new JSDOM(template);
var document = dom.window.document;
document.title += " - " + post.name;
document.querySelector('meta[property="og:title"]').setAttribute("content", post.name);
document.querySelector('meta[name="description"]').setAttribute("content", post.summary);
document.querySelector('meta[property="og:description"]').setAttribute("content", post.summary);
var nav = "";
nav += '<a class="nav-item nav-link" href="/#blog">Back to Main Page</a>';
if (i > 0)
nav += '<a class="nav-item nav-link" href="/blog-' + json[i - 1].id + '">Previous Post</a>';
if (i < json.length - 1)
nav += '<a class="nav-item nav-link" href="/blog-' + json[i + 1].id + '">Next Post</a>';
document.getElementById("nav-items").innerHTML = nav;
var c = "";
c += '<div class="list-display rounded">';
c += '<div class="blog-isolated">'
c += '<h1>' + post.name + '</h1>';
c += '<div id="blog-post-' + post.id + '">'
c += converter.makeHtml(content.toString());
c += '</div>';
c += '<span class="text-muted project-status blog-isolated-status">' + post.date + "</span>";
var discussLink = post.discuss;
if (discussLink)
c += '<a href="' + discussLink + '" class="blog-discuss" id="blog-discuss-' + post.id + '">Discuss this post</a>'
c += '</div></div>';
document.getElementById("main").innerHTML = c;
let html = dom.serialize();
fs.writeFile(folder + "blog-" + post.id + ".html", html, function (_, _) {});
});
}
});
});
}

View file

@ -0,0 +1,46 @@
// Modified version of https://github.com/Kriegslustig/showdown-footnotes
/*! showdown-prettify 06-01-2016 */
(function (extension) {
'use strict';
if (typeof showdown !== 'undefined') {
extension(showdown);
} else if (typeof define === 'function' && define.amd) {
define(['showdown'], extension);
} else if (typeof exports === 'object') {
module.exports = extension(require('showdown'));
} else {
throw Error('Could not find showdown library');
}
}(function (showdown) {
'use strict';
showdown.extension('footnotes', function () {
return [{
type: 'lang',
filter: function filter(text) {
return text.replace(/\[\^([\d\w]+)\]:\s*((\n+(\s{2,4}|\t).+)+)/mgi, function (str, name, rawContent, _, padding) {
var content = converter.makeHtml(rawContent.replace(new RegExp('^' + padding, 'gm'), ''));
return '<a class="blog-anchor" id="footnote-' + name + '"></a><div class="footnote">[' + name + ']:' + content + '</div>';
});
}
}, {
type: 'lang',
filter: function filter(text) {
return text.replace(/\[\^([\d\w]+)\]:( |\n)((.+\n)*.+)/mgi, function (str, name, _, content) {
return '<a class="blog-anchor" id="footnote-' + name + '"></a><small class="footnote"><a href="#footnote-reference-' + name + '">[' + name + ']</a>: ' + content + '</small>';
});
}
}, {
type: 'lang',
filter: function filter(text) {
return text.replace(/\[\^([\d\w]+)\]/mgi, function (str, name) {
return '<a class="blog-anchor" id="footnote-reference-' + name + '"><a href="#footnote-' + name + '"><sup>[' + name + ']</sup></a>';
});
}
}];
});
}));
//# sourceMappingURL=showdown-prettify.js.map

View file

@ -1,3 +1,5 @@
// Modified version of https://github.com/showdownjs/prettify-extension
/*! showdown-prettify 06-01-2016 */
(function (extension) {
'use strict';

View file

@ -2,22 +2,25 @@ const {
Feed
} = require("feed");
const fs = require("fs");
const showdown = require("showdown");
const converter = new showdown.Converter({
parseImgDimensions: true
});
const converter = require("./showdown")(1);
module.exports = function () {
let folder = __dirname + "/../";
createFeeds(folder);
fs.watchFile(folder + "blog/posts.json", function (curr, prev) {
if (curr.mtime == prev.mtime)
return;
console.log("Refreshing feeds...");
createFeed(function (feed) {
fs.writeFile(folder + "feed.json", feed.json1(), function (_, _) {});
fs.writeFile(folder + "rss.xml", feed.rss2(), function (_, _) {});
fs.writeFile(folder + "atom.xml", feed.atom1(), function (_, _) {});
});
createFeeds(folder);
});
}
function createFeeds(folder) {
console.log("Refreshing feeds...");
createFeed(function (feed) {
fs.writeFile(folder + "feed.json", feed.json1(), function (_, _) {});
fs.writeFile(folder + "rss.xml", feed.rss2(), function (_, _) {});
fs.writeFile(folder + "atom.xml", feed.atom1(), function (_, _) {});
});
}
@ -44,15 +47,14 @@ function createFeed(callback) {
let json = JSON.parse(data);
for (let i = json.length - 1; i >= 0; i--) {
let post = json[i];
let id = post["id"];
let date = new Date(post["date"]);
let date = new Date(post.date);
fs.readFile(__dirname + "/../blog/" + id + ".md", function (_, content) {
fs.readFile(__dirname + "/../blog/" + post.id + ".md", function (_, content) {
let html = converter.makeHtml(content.toString());
feed.addItem({
title: post["name"],
link: "https://ellpeck.de/#blog-" + id,
description: post["summary"],
title: post.name,
link: "https://ellpeck.de/blog-" + post.id,
description: post.summary,
content: html,
date: date,
published: date

View file

@ -1,2 +1,3 @@
require("./rss")();
require("./blog")();
require("./sitemap")();

10
node/showdown.js Normal file
View file

@ -0,0 +1,10 @@
const showdown = require("showdown");
require("./lib/showdown-prettify");
require("./lib/showdown-footnotes");
module.exports = function (headerLevel) {
return new showdown.Converter({
parseImgDimensions: true,
headerLevelStart: headerLevel,
extensions: ["prettify", "footnotes"]
});
}

View file

@ -5,48 +5,60 @@ const fs = require("fs");
module.exports = function () {
let folder = __dirname + "/../";
refreshSitemap(folder);
fs.watchFile(folder + "blog/posts.json", function (curr, prev) {
if (curr.mtime == prev.mtime)
return;
console.log("Refreshing sitemap...");
refreshSitemap(folder);
});
}
let sitemap = createSitemap({
hostname: 'https://ellpeck.de',
urls: [{
url: '/',
priority: 0.8
},
{
url: '/#projects',
changefreq: 'monthly'
},
{
url: '/#social',
changefreq: 'yearly'
},
{
url: '/#about',
changefreq: 'monthly'
},
{
url: '/#blog',
changefreq: 'weekly',
priority: 0.6
}
]
});
function refreshSitemap(folder) {
console.log("Refreshing sitemap...");
fs.readFile(folder + "blog/posts.json", function (_, data) {
let json = JSON.parse(data);
for (let post of json) {
sitemap.add({
url: "/#blog-" + post["id"],
priority: 0.4
});
let sitemap = createSitemap({
hostname: 'https://ellpeck.de',
urls: [{
url: '/',
priority: 0.8
},
{
url: '/#projects',
changefreq: 'monthly'
},
{
url: '/#social',
changefreq: 'yearly'
},
{
url: '/#about',
changefreq: 'monthly'
},
{
url: '/#blog',
changefreq: 'weekly',
priority: 0.6
},
{
url: '/foefrenzy',
priority: 0.7
},
{
url: '/foefrenzy/maps'
}
]
});
fs.writeFile(folder + "/sitemap.xml", sitemap.toXML(), function (_, _) {});
});
fs.readFile(folder + "blog/posts.json", function (_, data) {
let json = JSON.parse(data);
for (let post of json) {
sitemap.add({
url: "/blog-" + post.id
});
}
fs.writeFile(folder + "/sitemap.xml", sitemap.toXML(), function (_, _) {});
});
}

View file

@ -44,8 +44,8 @@ const questions = [{
a: 'German, English, Java, JavaScript, C# and a bit of C, HTML and CSS.'
},
{
"q": "How do you make games?",
"a": "I usually use the .NET-based framework <a href=\"http://www.monogame.net/\">MonoGame</a> together with some libraries to make it a bit easier, including my own library, MLEM, which you can read about in the <a href=\"#projects\">Projects</a> section."
q: "How do you make games?",
a: "I usually use the .NET-based framework <a href=\"http://www.monogame.net/\">MonoGame</a> together with some libraries to make it a bit easier, including my own library, MLEM, which you can read about in the <a href=\"#projects\">Projects</a> section."
},
{
q: 'What\'s your favorite programming language?',
@ -67,6 +67,10 @@ const questions = [{
q: 'How did you make this site?',
a: 'I made this page using <a href="https://getbootstrap.com/">Bootstrap</a>. The content is dynamically generated using JavaScript based on a bunch of JSON data. If you\'re curious about the (probably pretty messy) code, then you can check out the <a href="https://github.com/Ellpeck/Web">GitHub repository</a>.'
},
{
q: "What kind of music do you like?",
a: 'Mostly Rock and Pop. Sometimes I enjoy some good lofi hip hop music - beats to relax/study to, though. You can check out my <a href="https://www.last.fm/user/Ellpeck">Last.fm</a> if you\'re curious about specifics.'
},
{
q: 'What\'s a random fact about you?',
a: facts[Math.floor(Math.random() * facts.length)]
@ -76,8 +80,8 @@ const questions = [{
let a = '';
for (question of questions) {
a += '<p>';
a += '<strong>Q: ' + question["q"] + '</strong><br>';
a += '<strong>A:</strong> ' + question["a"];
a += '<strong>Q: ' + question.q + '</strong><br>';
a += '<strong>A:</strong> ' + question.a;
a += '</p>';
}
$('#about-list').html(a);

View file

@ -1,8 +1,3 @@
let converter = new showdown.Converter({
parseImgDimensions: true,
headerLevelStart: 3,
extensions: ["prettify"]
});
$.ajax({
dataType: "json",
url: "blog/posts.json",
@ -12,63 +7,15 @@ $.ajax({
list.html("");
for (let i = json.length - 1; i >= 0; i--) {
var obj = json[i];
let id = obj["id"];
let p = "";
p += '<a class="blog-anchor" id="blog-' + id + '"></a>';
p += '<div class="card bg-light blog-entry rounded-0">';
p += '<div class="card-body">';
p += '<a class="blog-button" id="blog-button-' + id + '"><h2 class="card-title">' + obj["name"] + '</h2></a>';
p += '<div class="card-text text-muted blog-summary" id="blog-summary-' + id + '">' + obj["summary"] + '</div>';
p += '<div class="card-text" id="blog-post-' + id + '"></div>';
p += '<span class="text-muted project-status">' + obj["date"] + "</span>";
var discussLink = obj["discuss"];
if (discussLink)
p += '<a href="' + discussLink + '" class="blog-discuss" id="blog-discuss-' + id + '"></a>'
p += '<h2 class="card-title"><a class="blog-button" href="/blog-' + obj.id + '">' + obj.name + '</a></h2>';
p += '<div class="card-text text-muted blog-summary">' + obj.summary + '</div>';
p += '<span class="text-muted project-status">' + obj.date + "</span>";
p += '</div></div>';
list.append(p);
$("#blog-button-" + id).on('click', function () {
var post = $("#blog-post-" + id);
if (post.html() !== "") {
post.html("");
var discuss = $("#blog-discuss-" + id);
if (discuss.length)
discuss.html("");
$("#blog-summary-" + id).show();
history.pushState(null, null, "#blog");
} else {
openBlogPost(id);
history.pushState(null, null, "#blog-" + id);
}
});
}
if (window.location.hash.startsWith("#blog-")) {
var anchor = $(window.location.hash);
if (anchor.length) {
openBlogPost(window.location.hash.substring(6), function () {
$('html, body').animate({
scrollTop: anchor.offset().top
}, 0)
});
}
}
}
});
function openBlogPost(id, onDone) {
$.get("blog/" + id + ".md", function (markdown) {
let html = converter.makeHtml(markdown);
$("#blog-post-" + id).html(html);
var discuss = $("#blog-discuss-" + id);
if (discuss.length)
discuss.html("Discuss this post");
$("#blog-summary-" + id).hide();
PR.prettyPrint();
if (onDone)
onDone();
});
}
});

View file

@ -18,6 +18,7 @@ const greetings = [
"Good evening!",
"Yo yo yo yo",
"Yo yo yo yo yo",
"Henlo"
"Henlo",
"Sul Sul"
];
$('#intro-text').html(greetings[Math.floor(Math.random() * greetings.length)]);

View file

@ -1,9 +1,8 @@
const projects = [{
name: "Foe Frenzy",
desc: "Foe Frenzy is a fast-paced fighting game where you battle up to three of your friends with random, short-lasting items in an attempt to be the last survivor. It's currently in development, and will be released in December 2019.",
desc: "Foe Frenzy is a fast-paced fighting game where you battle up to three of your friends with random, short-lasting items in an attempt to be the last survivor. It's currently in development, and it will be released in December 2019.",
links: {
"itch.io page": "https://ellpeck.itch.io/foefrenzy",
"Watch the trailer": "https://www.youtube.com/watch?v=Z7ZeuVBNuf4"
"Check it out": "/foefrenzy"
},
status: "In development",
icon: "ff"
@ -63,17 +62,16 @@ let p = '';
for (project of projects) {
p += '<div class="card bg-light project rounded-0">';
p += '<div class="card-body">';
p += '<img class="project-image" src="res/projects/' + project["icon"] + '.png" alt="">';
p += '<img class="project-image" src="res/projects/' + project.icon + '.png" alt="">';
p += '<h4 class="card-title">' + project["name"] + '</h4>';
p += '<p class="card-text">' + project["desc"] + '</p>';
if (project["status"])
p += '<span class="text-muted project-status">' + project["status"] + '</span>';
p += '<h4 class="card-title">' + project.name + '</h4>';
p += '<p class="card-text">' + project.desc + '</p>';
if (project.status)
p += '<span class="text-muted project-status">' + project.status + '</span>';
let links = project["links"];
if (links) {
for (let name in links) {
p += '<a href="' + links[name] + '" class="card-link btn ' + (dark ? "btn-outline-light" : "btn-outline-info") + ' rounded-0">' + name + '</a>';
if (project.links) {
for (let name in project.links) {
p += '<a href="' + project.links[name] + '" class="card-link btn ' + (dark ? "btn-outline-light" : "btn-outline-info") + ' rounded-0">' + name + '</a>';
}
}

View file

@ -19,6 +19,9 @@
Michael Patrick Kelly
Madsen
The Crazy Ex-Girlfriend Cast
Wir Sind Helden
Juli
MALINDA
*/
const quotes = [
@ -135,6 +138,25 @@ const quotes = [
"In one indescribable instant, it all falls into place",
"The darkness - he's handsome for a metaphor",
"This session is gonna be different",
"He gives me love kernels"
"He gives me love kernels",
"Vielleicht wärst du Seetang, ich wäre Krill",
"Wir wär'n der Seegang, und dann wär'n wir still",
"Lass uns verschwinden, wir lösen uns auf",
"Ja, ich weiß, es war 'ne geile Zeit",
"Uns war kein Weg zu weit",
"Ich fahr, bis es in den Beinen sticht",
"Mit dem Fahrrad durch die Nacht",
"Rote Ampeln, leere Stadt",
"Ballerina in your music box",
"Forever turning to the same tune",
"They stare, but do they care about what you can't rearrange?",
"Who's face is in the mirror today?",
"Set fire to the roof of your house",
"Angel wings at the bus stop",
"Halos left on top of the bar",
"Heaven doesn't want me now",
"I wanna feel happy days, happy days",
"Hey kid, don't quit your daydream yet",
"Hey kid, don't listen to your head"
];
$('#quote-text').html('<em>' + quotes[Math.floor(Math.random() * quotes.length)] + '</em>');

View file

@ -37,12 +37,12 @@ const socials = [{
let s = '';
for (social of socials) {
s += '<a class="btn ' + (dark ? "btn-dark" : "btn-light") + ' social-button rounded-0" href="' + social["link"] + '"">';
let icon = social["name"].toLowerCase();
if (dark && social["darkIcon"])
s += '<a class="btn ' + (dark ? "btn-dark" : "btn-light") + ' social-button rounded-0" href="' + social.link + '"">';
let icon = social.name.toLowerCase();
if (dark && social.darkIcon)
icon += '_dark';
s += '<img class="social-image" src="res/social/' + icon + '.png" alt="">';
s += social["name"];
s += social.name;
s += '</a>'
}
$('#social-list').html(s);

View file

@ -29,4 +29,14 @@ body {
.modal-content {
background-color: #404142;
}
.blog-button:link,
.blog-button:visited {
color: #d8d8d8 !important;
}
.blog-button:hover,
.blog-button:active {
color: white !important;
}

View file

@ -1,8 +1,13 @@
html {
position: relative;
min-height: 100%;
}
body {
margin-top: 106px;
position: relative;
font-family: Roboto;
background-color: #404142;
margin-bottom: 100px;
}
.main {
@ -29,6 +34,7 @@ body {
.jumbotron {
margin-bottom: 0;
margin-top: 40px;
}
.project-image {
@ -74,14 +80,22 @@ body {
}
.blog-anchor {
display: block;
display: inline-block;
position: relative;
top: -80px;
top: -86px;
visibility: hidden;
}
.blog-button {
cursor: pointer;
.blog-button:link,
.blog-button:visited {
color: rgb(73, 73, 73);
text-decoration: none;
}
.blog-button:hover,
.blog-button:active {
color: black;
text-decoration: none;
}
.blog-entry {
@ -93,16 +107,20 @@ body {
}
.blog-summary {
margin-bottom: 15px;
width: 90%;
width: 85%;
}
.footer {
position: absolute;
left: 0;
right: 0;
bottom: 0;
line-height: 30px;
background-color: #f5f5f5;
padding: 15px;
padding-left: 15px;
padding-right: 15px;
overflow: auto;
height: 60px;
line-height: 60px;
}
.impressum-data {
@ -138,6 +156,27 @@ body {
margin: auto;
}
.blog-isolated {
position: relative;
}
.blog-isolated-status {
bottom: 0 !important;
right: 0 !important;
}
.reading-mode {
position: absolute;
right: 20px;
top: 15px;
}
blockquote {
margin-left: 2em;
margin-right: 2em;
font-style: italic;
}
@media (min-width: 1200px) {
.navbar {
width: 1200px;
@ -162,6 +201,12 @@ body {
padding-left: 20px;
padding-right: 20px;
}
.reading-mode {
position: relative;
right: auto;
top: auto;
}
}
@media (max-width: 510px) {