Compare commits
5 commits
191ae2113c
...
f8ae416fb4
Author | SHA1 | Date | |
---|---|---|---|
Manuel Thalmann | f8ae416fb4 | ||
Manuel Thalmann | 3a62c8902f | ||
Manuel Thalmann | b17363b9e8 | ||
Manuel Thalmann | de0b81efac | ||
Manuel Thalmann | c130b72ab6 |
BIN
docs/DiagonalDown.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/DiagonalUp.png
Normal file
After Width: | Height: | Size: 13 KiB |
143
docs/Documentation.md
Normal file
|
@ -0,0 +1,143 @@
|
|||
---
|
||||
Header:
|
||||
Right: ConnectForce
|
||||
---
|
||||
# ConnectForce Dokumentation
|
||||
> `ConnectForce` ist eine grossartige Umsetzung des allzeit beliebten Spiels namens "4 Gewinnt!".
|
||||
> In diesem strategisch anspruchsvollen Spiel müssen Spieler ihre Spielsteine möglichst geschickt platzieren, um so 4 von ihnen zu verbinden und sich so als dem Gegner überlegen zu beweisen!
|
||||
>
|
||||
> Wirst du dazu imstande sein, über dem IQ und der Geschicklichkeit deines Gegners zu obsiegen?
|
||||
|
||||
Für die Umsetzung von `ConnectForce` wurden Standards und Technologien wie etwa `SJDON`, `SuiWeb`, JavaScript, CSS und HTML verwendet.
|
||||
|
||||
Das Ziel dieses Dokuments ist es, aufzuzeigen, welche Aspekte des Projekts in ihrer Umsetzung herausfordernd waren, wie diese Herausforderungen in Angriff genommen wurden und welche Fortschritte besonders erwähnenswert sind.
|
||||
|
||||
## Das Endprodukt
|
||||
Das Endprodukt ist ein "4 Gewinnt!" Spiel, welches sich in der Grösse automatisch so skaliert, dass es sich auf jeglichen Geräten übersichtlich betrachten und auch steuern lässt (wie etwa Tablets, Handys, Computer, Ultra Widescreen Monitore etc.).
|
||||
|
||||
Folgender Screenshot zeigt, wie die Anwendung in einem Chromium Browser aussieht:
|
||||
|
||||
![](Preview.png)
|
||||
|
||||
## Features
|
||||
Die Anwendung bringt einige nennenswerte Funktionen mit sich. Zu diesen zählen folgende:
|
||||
|
||||
- Die Fähigkeit, das Spiel jederzeit neu zu starten
|
||||
- Die Möglichkeit, das Spiel im lokalen Speicher des Browsers zu speichern und aus diesem zu laden
|
||||
- Die Funktion, einzelne Änderungen, die am Zustand des Spiels vorgenommen wurden, rückgängig zu machen
|
||||
|
||||
## Nutzung
|
||||
Die Anwendung wird mit Hilfe der `SJDON`-Notation zur Verfügung gestellt und kann somit problemlos mit der Bibliothek `SuiWeb` dargestellt und ausgeführt werden:
|
||||
|
||||
```js
|
||||
import { App } from "./Components.js";
|
||||
import { render } from "./SuiWeb.js";
|
||||
|
||||
/**
|
||||
* Initializes the board.
|
||||
*/
|
||||
function initialize()
|
||||
{
|
||||
render([App], document.querySelector("#game"));
|
||||
}
|
||||
|
||||
initialize();
|
||||
```
|
||||
|
||||
## Herausforderungen
|
||||
Einige Aspekte des Spiels stellten sich, obwohl es zunächst nicht den Anschein machte, als Herausforderungen heraus. Eine davon wird in diesem Kapitel im Details beschrieben.
|
||||
|
||||
### Bestimmung des Gewinners
|
||||
Teil der Aufgabe war es, anhand des Zustandes des Spielfelds zu entscheiden, ob und welcher Spieler gewonnen hat.
|
||||
|
||||
Der Weg für die Bestimmung des Gewinners, welcher in diesem Projekt verwendet wurde, wird im Folgenden genauer erklärt.
|
||||
|
||||
#### Grundlagen
|
||||
Es gibt genau 4 verschiedene Arten, in denen Figuren zueinander stehen können:
|
||||
|
||||
| Bezeichnung | Relative Koordinaten |
|
||||
| --------------------- | -------------------- |
|
||||
| Horizontal | `[0, 1]` |
|
||||
| Vertikal | `[1, 0]` |
|
||||
| Diagonal (nach unten) | `[1, 1]` |
|
||||
| Diagonal (nach oben) | `[1, -1]` |
|
||||
|
||||
Folgende Bilder dienen der Illustration:
|
||||
|
||||
- Horizontal:
|
||||
![](./Horizontal.png)
|
||||
- Vertikal:
|
||||
![](./Vertical.png)
|
||||
- Diagonal (nach unten):
|
||||
![](./DiagonalDown.png)
|
||||
- Diagonal (nach oben):
|
||||
![](./DiagonalUp.png)
|
||||
|
||||
Der Fakt, dass Chips nur in diesen 4 Relationen auftreten können, wird genutzt, um mit Hilfe mehrerer Iterationen zu prüfen, ob sich jeweils 4 Felder mit den genannten relativen Koordinaten zueinander in Relation stehen.
|
||||
|
||||
Für die Kontrolle, ob eine Person gewonnen hat, wird folgender Algorithmus verwendet:
|
||||
|
||||
```js
|
||||
// Iterate over all possible relative coordinates.
|
||||
for (let yOffset = 0; yOffset <= 1; yOffset++)
|
||||
{
|
||||
for (let xOffset = (yOffset === 1) ? -1 : 1; xOffset <= 1; xOffset++)
|
||||
{
|
||||
// Calculate upper and lower bounds of the x-axis.
|
||||
let lowerBound = Math.max(0, xOffset * (Game.#count - 1) * -1);
|
||||
let upperBound = Math.min(Game.#width, Game.#width - (xOffset * (Game.#count - 1)));
|
||||
|
||||
// Iterate over all possible x- and y-coordinates.
|
||||
for (let y = 0; y < (Game.#height - yOffset * (Game.#count - 1)); y++)
|
||||
{
|
||||
for (let x = lowerBound; x < upperBound; x++)
|
||||
{
|
||||
/**
|
||||
* @type {CellOwner[]}
|
||||
*/
|
||||
let tokens = [];
|
||||
|
||||
// Generate a list of all tokens in the current scope.
|
||||
for (let i = 0; i < Game.#count; i++)
|
||||
{
|
||||
tokens.push(this.board[y + i * yOffset][x + i * xOffset]);
|
||||
}
|
||||
|
||||
let player = tokens[0];
|
||||
|
||||
// Check whether the tokens indicate a win.
|
||||
if (
|
||||
player !== "" &&
|
||||
tokens.every((token) => token === player))
|
||||
{
|
||||
return player;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
```
|
||||
|
||||
Dieser Algorithmus ist dazu imstande, zu bestimmen, ob und welcher Spieler gewonnen hat. Dies funktioniert unabhängig davon, ob die Steine horizontal, vertikal oder diagonal verbunden sind.
|
||||
|
||||
Einige Kommentare im Code sollen dabei behilflich sein, die Funktionalität zu verstehen.
|
||||
|
||||
## Umsetzung
|
||||
Für das Umsetzen des Projekts wurde die Notation `SJDON` verwendet zusammen mit der ausgelieferten Bibliothek `SuiWeb` verwendet.
|
||||
|
||||
Komponenten mit Besonderheiten werden im folgenden Kapitel genauer beschrieben.
|
||||
|
||||
### Komponenten
|
||||
#### App
|
||||
Die App-Komponente beinhaltet das gesamte Spiel-Konstrukt, zeigt den aktuellen Zustand des Spiels an und verarbeitet Interaktionen, die der Benutzer mit dem Spiel macht.
|
||||
|
||||
Dies beinhaltet alle zuvor genannten Funktionen und das Vollziehen eines Spielzuges.
|
||||
|
||||
Zudem wird ein zum Aufrufenden ideal passendes Produkt in einem Werbungs-Banner angezeigt.
|
||||
|
||||
## MenuBar
|
||||
Die Menü-Leiste erlaubt es dem Besucher, die von ihm gewünschten Funktionen auszuführen.
|
||||
|
||||
Sollte eine Funktion nicht verfügbar sein, wird dies dem Nutzer auf verständlicher Weise angezeigt.
|
BIN
docs/Horizontal.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
docs/Preview.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
docs/Vertical.png
Normal file
After Width: | Height: | Size: 11 KiB |
19
gulpfile.ts
|
@ -44,6 +44,18 @@ export function Assets(): NodeJS.ReadWriteStream
|
|||
dest(context.StaticPath(context.AssetDirName)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the icon of the webpage.
|
||||
*
|
||||
* @returns
|
||||
* The resulting stream.
|
||||
*/
|
||||
export function Icon(): NodeJS.ReadWriteStream
|
||||
{
|
||||
return src(context.SourcePath("favicon.ico")).pipe(
|
||||
dest(context.StaticPath()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the web pages.
|
||||
*
|
||||
|
@ -69,6 +81,7 @@ export function Build(): Promise<void>
|
|||
JavaScript,
|
||||
Styles,
|
||||
Assets,
|
||||
Icon,
|
||||
WebPages
|
||||
])(
|
||||
(error) =>
|
||||
|
@ -159,6 +172,12 @@ export let Watch: TaskFunction = async (): Promise<void> =>
|
|||
Assets,
|
||||
BrowserSync(syncer, join(context.AssetDirName, "**", "*"))));
|
||||
|
||||
watch(
|
||||
context.SourcePath("favicon.ico"),
|
||||
series(
|
||||
Icon,
|
||||
BrowserSync(syncer, join("favicon.ico"))));
|
||||
|
||||
watch(
|
||||
context.SourcePath("**", "*.html"),
|
||||
series(
|
||||
|
|
BIN
src/assets/ad.gif
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
src/assets/adInfo.png
Normal file
After Width: | Height: | Size: 565 B |
BIN
src/favicon.ico
Normal file
After Width: | Height: | Size: 176 KiB |
|
@ -81,7 +81,7 @@ export function App()
|
|||
}
|
||||
},
|
||||
{
|
||||
className: `game ${game.winner ? `winner ${game.winner}` : ""}`
|
||||
className: `game ${game.winner ? `winner ${game.winner}` : game.currentPlayer}`
|
||||
},
|
||||
[
|
||||
Board,
|
||||
|
@ -94,7 +94,8 @@ export function App()
|
|||
`Player ${Constants.PLAYER_NAMES[game.winner]} wins!` :
|
||||
`It's player "${Constants.PLAYER_NAMES[game.currentPlayer]}"s turn`
|
||||
],
|
||||
[MenuBar, { game }]
|
||||
[MenuBar, { game }],
|
||||
[Ad]
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -198,3 +199,41 @@ export function Button({ content })
|
|||
...content
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a very serious ad.
|
||||
*
|
||||
* @returns {NodeDescriptor}
|
||||
* The rendered ad.
|
||||
*/
|
||||
export function Ad()
|
||||
{
|
||||
return [
|
||||
"div",
|
||||
{
|
||||
className: "ad",
|
||||
style: "text-align: center;"
|
||||
},
|
||||
[
|
||||
"div",
|
||||
{
|
||||
style: "display: inline-block; position: relative;"
|
||||
},
|
||||
[
|
||||
"a",
|
||||
{
|
||||
href: "https://www.youtube.com/watch?v=Wgt7JQdymsQ",
|
||||
target: "_blank"
|
||||
},
|
||||
["img", { src: "./assets/ad.gif" }],
|
||||
[
|
||||
"img",
|
||||
{
|
||||
src: "./assets/adInfo.png",
|
||||
style: "position: absolute; top: 0; right: 0;"
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
|
5
src/js/types.d.ts
vendored
|
@ -48,12 +48,13 @@ type TextDescriptor = string;
|
|||
type ElementDescriptor = [
|
||||
tag: string,
|
||||
// eslint-disable-next-line @typescript-eslint/array-type
|
||||
...args: (NodeDescriptor | (Partial<HTMLElement> & Record<string, any>))[]
|
||||
...args: (NodeDescriptor | (Partial<HTMLElement> | Record<string, any>))[]
|
||||
];
|
||||
|
||||
/**
|
||||
* Represents a component in the SJDON notation.
|
||||
*/
|
||||
type FunctionNode = [
|
||||
fn: (...args: any[]) => NodeDescriptor, ...args: any[]
|
||||
fn: (...args: any[]) => NodeDescriptor,
|
||||
...args: any[]
|
||||
];
|
||||
|
|
|
@ -44,11 +44,11 @@ div {
|
|||
float: left;
|
||||
}
|
||||
|
||||
.game.winner.r .board, .game.winner.r .log {
|
||||
.game.winner.r .board, .game.r .log {
|
||||
background-color: pink;
|
||||
}
|
||||
|
||||
.game.winner.b .board, .game.winner.b .log {
|
||||
.game.winner.b .board, .game.b .log {
|
||||
background-color: lightblue;
|
||||
}
|
||||
|
||||
|
@ -73,6 +73,8 @@ div {
|
|||
.log {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
@ -87,3 +89,7 @@ div {
|
|||
.game .menu-bar button {
|
||||
margin: 0 5px 5px 0;
|
||||
}
|
||||
|
||||
.ad {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
|