Compare commits

..

3 commits

31 changed files with 613 additions and 68 deletions

6
.env Normal file
View file

@ -0,0 +1,6 @@
DB_Host: 'db'
DB_Port: 3306
DB_Name: 'MatheApp'
DB_Charset: 'utf8mb4'
DB_User: 'MatheApp'
DB_Password: 'password'

View file

@ -1,5 +1,5 @@
# Verwende ein Basisimage mit PHP # Verwende ein Basisimage mit PHP
FROM php:8.4-cli FROM php:8.2-cli
# Setze das Arbeitsverzeichnis # Setze das Arbeitsverzeichnis
WORKDIR /app WORKDIR /app

View file

@ -1,33 +0,0 @@
# The Math Wizard
The Math Wizard is a browser game that helps children automate the 1x1. It acts as a dungeon crawler game in which children can fight monsters by solving multiplication problems.
## Requirements
To run the game, you need Docker and Docker-Compose. Make sure that these are installed and configured on your system.
## Installation
1. clone the repository with `git clone https://github.com/PatrykHegenberg/TheMathWizard.git`.
2. change to the project directory with `cd TheMathWizard`.
3. run `docker-compose up --build` to install and start the game.
Once the installation is complete, you can access the game via your web browser under `localhost:8080` or the computer name.
## Usage
To create a new account, go to `localhost:8080/signup` in the browser. After signing up, you can start the game and begin fighting monsters and solving multiplication tasks. Your progress will be saved persistently on the hard disk of the host computer.
## License
This project is licensed under the MIT license. For more information, see the LICENSE file.
## Acknowledgments
This project uses the following resources:
- [ROT.js](https://github.com/ondras/rot.js/) (BSD-Lizenz)
- [Micro Rogue tileset](https://kenney.nl/assets/micro-roguelike) von Kenney.nl (CC0 1.0 Universelle Lizenz)
- [NES.css](https://nostalgic-css.github.io/NES.css/) (MIT Lizenz)
- [sfxr.me](https://www.sfxr.me/) (Öffentliches Domain)
- [Pixel Coin Bild](https://opengameart.org/content/pixel-coins) (CC-BY 3.0 Lizenz)
- [Roguelike-Browser-Boilerplate](https://github.com/chr15m/roguelike-browser-boilerplate)

View file

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 210 B

After

Width:  |  Height:  |  Size: 210 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 249 B

After

Width:  |  Height:  |  Size: 249 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 328 B

After

Width:  |  Height:  |  Size: 328 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 234 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 232 B

After

Width:  |  Height:  |  Size: 232 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 246 B

After

Width:  |  Height:  |  Size: 246 B

Before After
Before After

View file

@ -20,6 +20,7 @@ h1 {
font-size: 2em; font-size: 2em;
margin: 0.67em 0; margin: 0.67em 0;
} }
figcaption, figcaption,
figure, figure,
main { main {
@ -50,7 +51,8 @@ img {
max-width: 100%; max-width: 100%;
vertical-align: middle; vertical-align: middle;
} }
.site-header img{
.site-header img {
height: 60px; height: 60px;
} }
@ -84,6 +86,7 @@ h6,
color: #2e1e26; color: #2e1e26;
font-weight: 600; font-weight: 600;
} }
h1, h1,
.h1 { .h1 {
font-size: 38px; font-size: 38px;
@ -119,6 +122,7 @@ h2,
margin-top: 48px; margin-top: 48px;
margin-bottom: 16px; margin-bottom: 16px;
} }
h3, h3,
.h3 { .h3 {
margin-top: 36px; margin-top: 36px;
@ -141,9 +145,11 @@ p {
.container { .container {
max-width: 1128px; max-width: 1128px;
} }
.container-sm { .container-sm {
max-width: 848px; max-width: 848px;
} }
.container .container-sm { .container .container-sm {
max-width: 800px; max-width: 800px;
padding-left: 0; padding-left: 0;
@ -167,6 +173,7 @@ p {
padding: 12px 0; padding: 12px 0;
z-index: 2; z-index: 2;
} }
.site-header-inner { .site-header-inner {
position: relative; position: relative;
display: flex; display: flex;
@ -179,8 +186,8 @@ p {
text-align: center; text-align: center;
padding-top: 48px; padding-top: 48px;
padding-bottom: 88px; padding-bottom: 88px;
background-image: url("./../../images/HeroBanner.png"); background-image: url("/static/images/HeroBanner.png");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
} }
@ -192,15 +199,18 @@ footer img {
.hero-copy { .hero-copy {
position: relative; position: relative;
} }
.hero-paragraph { .hero-paragraph {
margin-bottom: 32px; margin-bottom: 32px;
} }
.hero-cta { .hero-cta {
max-width: 400px; max-width: 400px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 80px; margin-bottom: 80px;
} }
.lights-toggle { .lights-toggle {
color: rgba(107, 122, 144, 0.64); color: rgba(107, 122, 144, 0.64);
} }
@ -211,10 +221,12 @@ footer img {
padding-top: 88px; padding-top: 88px;
padding-bottom: 120px; padding-bottom: 120px;
} }
.hero-inner { .hero-inner {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.hero-copy { .hero-copy {
padding-top: 40px; padding-top: 40px;
padding-right: 48px; padding-right: 48px;
@ -222,33 +234,42 @@ footer img {
max-width: 512px; max-width: 512px;
z-index: 1; z-index: 1;
} }
.hero-title { .hero-title {
margin-bottom: 16px; margin-bottom: 16px;
} }
.hero-paragraph { .hero-paragraph {
margin-bottom: 32px; margin-bottom: 32px;
} }
.hero-cta { .hero-cta {
display: flex; display: flex;
align-items: center; align-items: center;
margin: 0; margin: 0;
} }
.hero-cta .button { .hero-cta .button {
min-width: 170px; min-width: 170px;
} }
.hero-cta .button:first-child { .hero-cta .button:first-child {
margin-right: 32px; margin-right: 32px;
} }
.header-illustration { .header-illustration {
display: block; display: block;
} }
.hero-media { .hero-media {
z-index: 0; z-index: 0;
} }
.hero-media img, .hero-media img,
.hero-media svg { .hero-media svg {
max-width: none; max-width: none;
} }
.header-illustration-image { .header-illustration-image {
display: block; display: block;
position: absolute; position: absolute;
@ -258,6 +279,7 @@ footer img {
height: 324px; height: 324px;
} }
} }
.features-wrap { .features-wrap {
max-width: 540px; max-width: 540px;
margin: 0 auto; margin: 0 auto;
@ -267,21 +289,26 @@ footer img {
text-align: center; text-align: center;
margin-bottom: 48px; margin-bottom: 48px;
} }
.feature:last-of-type { .feature:last-of-type {
margin-bottom: 0; margin-bottom: 0;
} }
.feature-icon { .feature-icon {
display: inline-flex; display: inline-flex;
margin-bottom: 16px; margin-bottom: 16px;
} }
.feature-icon img, .feature-icon img,
.feature-icon svg { .feature-icon svg {
max-width: none; max-width: none;
} }
.feature-title { .feature-title {
position: relative; position: relative;
margin-bottom: 26px; margin-bottom: 26px;
} }
.feature-title::after { .feature-title::after {
content: ''; content: '';
width: 32px; width: 32px;
@ -291,20 +318,25 @@ footer img {
left: calc(50% - 16px); left: calc(50% - 16px);
background: #e9edf3; background: #e9edf3;
} }
.feature-title::after {
.feature-title::after {
background: #3f2a34; background: #3f2a34;
} }
@media (min-width: 641px) { @media (min-width: 641px) {
.features { .features {
position: relative; position: relative;
} }
.features .section-inner { .features .section-inner {
padding-bottom: 100px; padding-bottom: 100px;
} }
.features .section-paragraph { .features .section-paragraph {
padding-left: 72px; padding-left: 72px;
padding-right: 72px; padding-right: 72px;
} }
.features::before { .features::before {
content: ''; content: '';
width: 100%; width: 100%;
@ -312,40 +344,47 @@ footer img {
position: absolute; position: absolute;
left: 0; left: 0;
top: 168px; top: 168px;
background: linear-gradient( background: linear-gradient(to bottom,
to bottom, #3f2a34,
#3f2a34, #3f2a34);
#3f2a34
);
} }
.features::before {
.features::before {
display: none; display: none;
} }
.feature { .feature {
text-align: left; text-align: left;
} }
.feature-inner { .feature-inner {
display: flex; display: flex;
} }
.feature-icon { .feature-icon {
display: block; display: block;
margin-top: 8px; margin-top: 8px;
margin-right: 32px; margin-right: 32px;
margin-bottom: 0; margin-bottom: 0;
} }
.feature-title::after { .feature-title::after {
left: 0; left: 0;
} }
} }
.cta { .cta {
position: relative; position: relative;
} }
.cta .section-inner { .cta .section-inner {
padding-bottom: 64px; padding-bottom: 64px;
} }
.cta .section-paragraph { .cta .section-paragraph {
margin-bottom: 32px; margin-bottom: 32px;
} }
.cta::before { .cta::before {
content: ''; content: '';
position: absolute; position: absolute;
@ -354,32 +393,39 @@ footer img {
height: 263px; height: 263px;
width: 1440px; width: 1440px;
} }
.cta-cta { .cta-cta {
max-width: 400px; max-width: 400px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
@media (max-width: 639px) { @media (max-width: 639px) {
.cta-cta .button { .cta-cta .button {
display: flex; display: flex;
} }
} }
@media (min-width: 641px) { @media (min-width: 641px) {
.cta .section-inner { .cta .section-inner {
padding-bottom: 128px; padding-bottom: 128px;
} }
.cta .section-paragraph { .cta .section-paragraph {
margin-bottom: 40px; margin-bottom: 40px;
padding-left: 72px; padding-left: 72px;
padding-right: 72px; padding-right: 72px;
} }
.cta::before { .cta::before {
bottom: 0; bottom: 0;
} }
} }
.is-boxed { .is-boxed {
background: #e9edf3; background: #e9edf3;
} }
.body-wrap { .body-wrap {
background: #fff; background: #fff;
overflow: hidden; overflow: hidden;
@ -387,14 +433,17 @@ footer img {
flex-direction: column; flex-direction: column;
min-height: 100vh; min-height: 100vh;
} }
.boxed-container { .boxed-container {
max-width: 1440px; max-width: 1440px;
margin: 0 auto; margin: 0 auto;
box-shadow: 0 16px 48px rgba(255, 255, 255, 0.5); box-shadow: 0 16px 48px rgba(255, 255, 255, 0.5);
} }
main { main {
flex: 1 0 auto; flex: 1 0 auto;
} }
.section-inner { .section-inner {
position: relative; position: relative;
padding-top: 48px; padding-top: 48px;
@ -432,6 +481,7 @@ main {
margin: 0 auto; margin: 0 auto;
height: 500px; height: 500px;
} }
.container2 { .container2 {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -440,6 +490,7 @@ main {
margin: 0 auto; margin: 0 auto;
height: 750px; height: 750px;
} }
.container3 { .container3 {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -450,6 +501,7 @@ main {
background-color: #2e1e26; background-color: #2e1e26;
padding: 20px; padding: 20px;
} }
.container4 { .container4 {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -460,6 +512,7 @@ main {
background-color: #2e1e26; background-color: #2e1e26;
padding: 20px; padding: 20px;
} }
.footer-copyright { .footer-copyright {
flex: none; flex: none;
width: 80%; width: 80%;
@ -467,6 +520,7 @@ main {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.register { .register {
height: 1200px; height: 1200px;
display: flex; display: flex;
@ -490,6 +544,7 @@ main {
.nes-field { .nes-field {
max-width: 400px; max-width: 400px;
} }
.login { .login {
height: 1200px; height: 1200px;
display: flex; display: flex;
@ -510,6 +565,7 @@ main {
border-radius: 5px; border-radius: 5px;
box-shadow: #3f2a34; box-shadow: #3f2a34;
} }
.learnContainer { .learnContainer {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -523,6 +579,7 @@ main {
border-radius: 5px; border-radius: 5px;
box-shadow: 0 0 4px #433e4c box-shadow: 0 0 4px #433e4c
} }
.profile { .profile {
height: 1200px; height: 1200px;
display: flex; display: flex;
@ -532,58 +589,68 @@ main {
border-radius: 5px; border-radius: 5px;
box-shadow: #3f2a34; box-shadow: #3f2a34;
} }
.container .login { .container .login {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
text-align: center; text-align: center;
} }
.asset-dark { .asset-dark {
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
} }
.is-loaded .asset-dark { .is-loaded .asset-dark {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
} }
.asset-dark { .asset-dark {
display: none; display: none;
} }
.asset-dark { .asset-dark {
display: block; display: block;
} }
a {
a {
color: #8595ae; color: #8595ae;
} }
h1,
h2, h1,
h3, h2,
h4, h3,
h5, h4,
h6, h5,
.h1, h6,
.h2, .h1,
.h3, .h2,
.h4, .h3,
.h5, .h4,
.h6 { .h5,
.h6 {
color: #fff !important; color: #fff !important;
} }
.is-boxed { .is-boxed {
background: #3f2a34; background: #3f2a34;
} }
.body-wrap {
.body-wrap {
background: #2e1e26; background: #2e1e26;
} }
.boxed-container {
.boxed-container {
box-shadow: 0 16px 48px #433e4c; box-shadow: 0 16px 48px #433e4c;
} }
.has-top-divider { .has-top-divider {
position: relative; position: relative;
} }
.has-top-divider::before {
.has-top-divider::before {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
@ -593,10 +660,12 @@ main {
height: 1px; height: 1px;
background: #3f2a34; background: #3f2a34;
} }
.has-bottom-divider {
.has-bottom-divider {
position: relative; position: relative;
} }
.has-bottom-divider::after {
.has-bottom-divider::after {
content: ''; content: '';
position: absolute; position: absolute;
bottom: 0; bottom: 0;
@ -606,6 +675,7 @@ main {
height: 1px; height: 1px;
background: #3f2a34; background: #3f2a34;
} }
body, body,
a, a,
h1, h1,
@ -625,4 +695,4 @@ h6,
.boxed-container { .boxed-container {
transition: box-shadow 0.15s ease; transition: box-shadow 0.15s ease;
} }

148
db.go Normal file
View file

@ -0,0 +1,148 @@
package main
import (
"crypto/sha256"
"database/sql"
"encoding/hex"
"fmt"
"os"
_ "github.com/go-sql-driver/mysql"
"github.com/joho/godotenv"
"github.com/labstack/echo/v4"
)
var db *sql.DB
func init() {
err := godotenv.Load()
if err != nil {
fmt.Println("Error loading .env file")
os.Exit(1)
}
dbHost := os.Getenv("DB_Host")
dbPort := os.Getenv("DB_Port")
dbName := os.Getenv("DB_Name")
dbCharset := os.Getenv("DB_Charset")
dbUser := os.Getenv("DB_User")
dbPassword := os.Getenv("DB_Password")
// Connect to the database
db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s", dbUser, dbPassword, dbHost, dbPort, dbName, dbCharset))
// db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(db:3306)/%s?charset=utf8mb4", dbUser, dbPassword, dbName))
if err != nil {
fmt.Println("Error connecting to the database")
os.Exit(1)
}
}
// User struct
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Vorname string `json:"vorname"`
Nachname string `json:"nachname"`
Email string `json:"email"`
Password string `json:"password"`
LessonCount int `json:"lesson_count"`
Level int `json:"level"`
XP int `json:"xp"`
Coins int `json:"coins"`
IsAdmin sql.NullBool `json:"is_admin"`
}
// CRUD operations
func getUsers(c echo.Context) error {
rows, err := db.Query("SELECT * FROM user")
if err != nil {
return err
}
defer rows.Close()
users := []User{}
for rows.Next() {
user := User{}
err := rows.Scan(&user.ID, &user.Username, &user.Vorname, &user.Nachname, &user.Email, &user.Password, &user.LessonCount, &user.Level, &user.XP, &user.Coins, &user.IsAdmin)
if err != nil {
return err
}
users = append(users, user)
}
return c.JSON(200, users)
}
func getUser(c echo.Context) error {
id := c.Param("id")
row := db.QueryRow("SELECT * FROM user WHERE id = ?", id)
user := User{}
err := row.Scan(&user.ID, &user.Username, &user.Vorname, &user.Nachname, &user.Email, &user.Password, &user.LessonCount, &user.Level, &user.XP, &user.Coins, &user.IsAdmin)
if err != nil {
return err
}
return c.JSON(200, user)
}
func createUser(c echo.Context) error {
user := new(User)
if err := c.Bind(user); err != nil {
return err
}
// Hash das Passwort
user.Password = hashPassword(user.Password)
result, err := db.Exec("INSERT INTO user (username, vorname, nachname, email, password, lesson_count, level, xp, coins, isAdmin) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
user.Username, user.Vorname, user.Nachname, user.Email, user.Password, user.LessonCount, user.Level, user.XP, user.Coins, user.IsAdmin)
if err != nil {
return err
}
lastInsertID, err := result.LastInsertId()
if err != nil {
return err
}
user.ID = int(lastInsertID)
return c.JSON(201, user)
}
func updateUser(c echo.Context) error {
id := c.Param("id")
user := new(User)
if err := c.Bind(user); err != nil {
return err
}
_, err := db.Exec("UPDATE user SET username = ?, vorname = ?, nachname = ?, email = ?, password = ?, lesson_count = ?, level = ?, xp = ?, coins = ?, isAdmin = ? WHERE id = ?",
user.Username, user.Vorname, user.Nachname, user.Email, user.Password, user.LessonCount, user.Level, user.XP, user.Coins, user.IsAdmin, id)
if err != nil {
return err
}
return c.NoContent(204)
}
func deleteUser(c echo.Context) error {
id := c.Param("id")
_, err := db.Exec("DELETE FROM user WHERE id = ?", id)
if err != nil {
return err
}
return c.NoContent(204)
}
func hashPassword(password string) string {
hash := sha256.Sum256([]byte(password))
return hex.EncodeToString(hash[:])
}

View file

@ -4,6 +4,7 @@ services:
web: web:
#image: math_wizard #image: math_wizard
build: build:
context: /home/pata/dev/WebProg/TheMathWizard
dockerfile: Dockerfile.math_wizard dockerfile: Dockerfile.math_wizard
ports: ports:
- "8080:8080" - "8080:8080"
@ -14,6 +15,7 @@ services:
db: db:
build: build:
context: /home/pata/dev/WebProg/TheMathWizard
dockerfile: Dockerfile.db dockerfile: Dockerfile.db
environment: environment:
MYSQL_ROOT_PASSWORD: password MYSQL_ROOT_PASSWORD: password

24
go.mod Normal file
View file

@ -0,0 +1,24 @@
module the_math_wizard
go 1.21.5
require (
github.com/chasefleming/elem-go v0.17.0
github.com/joho/godotenv v1.5.1
github.com/labstack/echo/v4 v4.11.4
)
require (
github.com/go-sql-driver/mysql v1.7.1
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
)

41
go.sum Normal file
View file

@ -0,0 +1,41 @@
github.com/chasefleming/elem-go v0.17.0 h1:9hqg6OSacy2YgHLpY/soZqXLOOMfhmKG5qukrSgjxlI=
github.com/chasefleming/elem-go v0.17.0/go.mod h1:hz73qILBIKnTgOujnSMtEj20/epI+f6vg71RUilJAA4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,3 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}

78
server.go Normal file
View file

@ -0,0 +1,78 @@
package main
import (
// "fmt"
"net/http"
// "os"
// "os/exec"
// "runtime"
// "strconv"
// "github.com/chasefleming/elem-go"
// "github.com/chasefleming/elem-go/attrs"
// "github.com/chasefleming/elem-go/htmx"
"the_math_wizard/views"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Static("/static", "assets")
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Routes
e.GET("/", RenderIndexRoute)
e.GET("/learn", RenderLearnRoute)
e.GET("/mathe", RenderMatheRoute)
e.GET("/game", RenderGameRoute)
e.GET("/login", RenderLoginRoute)
e.GET("/register", RenderRegisterRoute)
e.GET("/profile", RenderProfileRoute)
e.GET("/logout", LogoutRoute)
e.GET("/delete", DeleteRoute)
e.GET("/updateData", RenderUpdateRoute)
e.GET("/deleteUser", RenderDeleteRoute)
// Start the server
e.Logger.Fatal(e.Start(":3000"))
}
func RenderIndexRoute(c echo.Context) error {
return c.HTML(http.StatusOK, views.RenderIndex())
}
func RenderLearnRoute(c echo.Context) error {
return nil
}
func RenderMatheRoute(c echo.Context) error {
return nil
}
func RenderGameRoute(c echo.Context) error {
return nil
}
func RenderLoginRoute(c echo.Context) error {
return nil
}
func RenderRegisterRoute(c echo.Context) error {
return nil
}
func RenderProfileRoute(c echo.Context) error {
return nil
}
func LogoutRoute(c echo.Context) error {
return nil
}
func DeleteRoute(c echo.Context) error {
return nil
}
func RenderUpdateRoute(c echo.Context) error {
return nil
}
func RenderDeleteRoute(c echo.Context) error {
return nil
}

8
views/footer.templ.go Normal file
View file

@ -0,0 +1,8 @@
package views
import "github.com/chasefleming/elem-go"
func RenderFooter() elem.Node {
footerContent := elem.Footer(nil)
return footerContent
}

21
views/head.templ.go Normal file
View file

@ -0,0 +1,21 @@
package views
import (
"github.com/chasefleming/elem-go"
"github.com/chasefleming/elem-go/attrs"
)
func RenderHead() elem.Node {
headContent := elem.Head(nil,
elem.Meta(attrs.Props{attrs.Charset: "UTF-8", attrs.Name: "viewport", attrs.Content: "width=device-width, initial-scale=1.0"}),
elem.Meta(attrs.Props{attrs.HTTPequiv: "X-UA-Compatible", attrs.Content: "IE=edge"}),
elem.Script(attrs.Props{attrs.Src: "https://unpkg.com/htmx.org"}),
elem.Title(nil, elem.Text("The Math Wizard")),
elem.Link(attrs.Props{attrs.Href: "https://cdn.jsdelivr.net/npm/nes.css@2.3.0/css/nes.min.css", attrs.Rel: "stylesheet"}),
elem.Link(attrs.Props{attrs.Rel: "stylesheet", attrs.Href: "https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css"}),
elem.Link(attrs.Props{attrs.Rel: "stylesheet", attrs.Href: "/static/styles/style.css"}),
elem.Script(attrs.Props{attrs.Src: "https://unpkg.com/scrollreveal@4.0.0/dist/scrollreveal.min.js"}),
elem.Style(nil, elem.Text("@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P');")),
)
return headContent
}

24
views/index.templ.go Normal file
View file

@ -0,0 +1,24 @@
package views
import (
"github.com/chasefleming/elem-go"
"github.com/chasefleming/elem-go/attrs"
)
func RenderIndex() string {
headContent := RenderHead()
navContent := RenderNav()
mainContent := RenderMain()
footerContent := RenderFooter()
bodyContent := elem.Body(attrs.Props{attrs.Class: "is-boxed"},
elem.Div(attrs.Props{attrs.Class: "body-wrap boxed-container"},
navContent,
mainContent,
footerContent,
),
)
htmlContent := elem.Html(nil, headContent, bodyContent)
return htmlContent.Render()
}

94
views/main.templ.go Normal file
View file

@ -0,0 +1,94 @@
package views
import (
"github.com/chasefleming/elem-go"
"github.com/chasefleming/elem-go/attrs"
)
func RenderMain() elem.Node {
mainContent := elem.Main(nil,
elem.Section(attrs.Props{attrs.Class: "hero"},
elem.Div(attrs.Props{attrs.Class: "container"},
elem.Div(attrs.Props{attrs.Class: "hero-inner"},
elem.Div(attrs.Props{attrs.Class: "hero-copy"},
elem.H1(attrs.Props{attrs.Class: "hero-title"}, elem.Text("Automatisiere spielerisch das 1x1")),
elem.P(attrs.Props{attrs.Class: "hero-paragraph"}, elem.Text("Zeige was du kannst und kämpfe dich durch den Dungeon.")),
elem.Div(attrs.Props{attrs.Class: "hero-cta"},
elem.A(attrs.Props{attrs.Class: "nes-btn is-primary", attrs.Href: "/login"}, elem.Text("Starte Jetzt!")),
),
),
),
),
),
elem.Section(attrs.Props{attrs.Class: "features section"},
elem.Div(attrs.Props{attrs.Class: "container"},
elem.Div(attrs.Props{attrs.Class: "features-inner section-inner has-bottom-divider"},
elem.Div(attrs.Props{attrs.Class: "features-header text-center"},
elem.Div(attrs.Props{attrs.Class: "container-sm"},
elem.H2(attrs.Props{attrs.Class: "section-title"}, elem.Text("The Math Wizard")),
elem.P(attrs.Props{attrs.Class: "section-paragraph"}, elem.Text("Ohne es zu bemerken lernst du das 1x1.")),
),
),
elem.Div(attrs.Props{attrs.Class: "features-wrap"},
elem.Div(attrs.Props{attrs.Class: "feature is-revealing"},
elem.Div(attrs.Props{attrs.Class: "feature-inner"},
elem.Div(attrs.Props{attrs.Class: "feature-icon"},
elem.Img(attrs.Props{attrs.Src: "/static/images/weapon_sword_ruby.png", attrs.Alt: "Feature 01"})),
elem.Div(attrs.Props{attrs.Class: "feature-content"},
elem.H3(attrs.Props{attrs.Class: "feature-title"}, elem.Text("Immer neue Level")),
elem.P(attrs.Props{attrs.Class: "text-sm mb-0"}, elem.Text("Spannende, zufallsgeneriete Level im Dungeon-Crawler-Stil.")),
),
),
),
elem.Div(attrs.Props{attrs.Class: "feature is-revealing"},
elem.Div(attrs.Props{attrs.Class: "feature-inner"},
elem.Div(attrs.Props{attrs.Class: "feature-icon"},
elem.Img(attrs.Props{attrs.Src: "/static/images/monster_imp.png", attrs.Alt: "Feature 02"})),
elem.Div(attrs.Props{attrs.Class: "feature-content"},
elem.H3(attrs.Props{attrs.Class: "feature-title"}, elem.Text("Angepasste Schwierigkeit")),
elem.P(attrs.Props{attrs.Class: "text-sm mb-0"}, elem.Text("Die Aufgaben passen sich deinen aktuellen Fähigkeiten an.")),
),
),
),
elem.Div(attrs.Props{attrs.Class: "feature is-revealing"},
elem.Div(attrs.Props{attrs.Class: "feature-inner"},
elem.Div(attrs.Props{attrs.Class: "feature-icon"},
elem.Img(attrs.Props{attrs.Src: "/static/images/npc_knight_green.png", attrs.Alt: "Feature 03"})),
elem.Div(attrs.Props{attrs.Class: "feature-content"},
elem.H3(attrs.Props{attrs.Class: "feature-title"}, elem.Text("Fokus auf das was zählt")),
elem.P(attrs.Props{attrs.Class: "text-sm mb-0"}, elem.Text("Fokus auf das Automatisieren der Multiplikation im Zahlenraum von 1 bis 10.")),
),
),
),
elem.Div(attrs.Props{attrs.Class: "feature is-revealing"},
elem.Div(attrs.Props{attrs.Class: "feature-inner"},
elem.Div(attrs.Props{attrs.Class: "feature-icon"},
elem.Img(attrs.Props{attrs.Src: "/static/images/monster_necromancer.png", attrs.Alt: "Feature 04"})),
elem.Div(attrs.Props{attrs.Class: "feature-content"},
elem.H3(attrs.Props{attrs.Class: "feature-title"}, elem.Text("Lernfortschritt immer im Blick")),
elem.P(attrs.Props{attrs.Class: "text-sm mb-0"}, elem.Text("Lernziel und Fortschrittskontrolle, um deinen Fortschritt zu verfolgen.")),
),
),
),
),
),
),
),
elem.Section(attrs.Props{attrs.Class: "cta section"},
elem.Div(attrs.Props{attrs.Class: "container-sm"},
elem.Div(attrs.Props{attrs.Class: "cta-inner section-inner"},
elem.Div(attrs.Props{attrs.Class: "cta-header text-center"},
elem.H2(attrs.Props{attrs.Class: "section-title"}, elem.Text("Bereit los zu legen?")),
elem.P(attrs.Props{attrs.Class: "section-paragraph"}, elem.Text("Fange noch heute an zu lernen.")),
elem.Div(attrs.Props{attrs.Class: "cta-cta"},
elem.A(attrs.Props{attrs.Class: "nes-btn is-primary", attrs.Href: "/login"},
elem.Text("Los geht's")),
),
),
),
),
),
)
return mainContent
}

19
views/nav.templ.go Normal file
View file

@ -0,0 +1,19 @@
package views
import (
"github.com/chasefleming/elem-go"
"github.com/chasefleming/elem-go/attrs"
)
func RenderNav() elem.Node {
navContent := elem.Header(attrs.Props{attrs.Class: "site-header"},
elem.Div(attrs.Props{attrs.Class: "container"},
elem.Nav(nil,
elem.Div(attrs.Props{attrs.Class: "site-header-inner"},
elem.A(attrs.Props{attrs.Class: "", attrs.Href: "/"}),
),
),
),
)
return navContent
}

46
views/profile.templ.go Normal file
View file

@ -0,0 +1,46 @@
package views
import (
"fmt"
"github.com/chasefleming/elem-go"
"github.com/chasefleming/elem-go/attrs"
)
type Stats struct {
Username string
}
func RenderProfile() elem.Node {
stats := Stats{
Username: "TestUser",
}
page := elem.Div(attrs.Props{attrs.Class: "profile"},
elem.Div(attrs.Props{attrs.Style: "padding: 20px; border-radius: 5px; box-shadow: 0 0 4px #433e4c; margin-top: 20px; margin-bottom: 20px; background-color: #2e1e26;"},
elem.Div(attrs.Props{attrs.Class: "container3"},
elem.Section(attrs.Props{attrs.Class: "message-list"},
elem.Section(attrs.Props{attrs.Class: "message -left"},
elem.I(attrs.Props{attrs.Class: "nes-bcrikko"}),
elem.Div(attrs.Props{attrs.Class: "nes-balloon from-left"},
elem.P(nil, elem.Text(fmt.Sprintf("Hallo %s zurück. Wie kann ich dir helfen?", stats.Username))),
),
),
),
elem.Div(attrs.Props{attrs.Class: "container3"},
elem.Div(attrs.Props{attrs.Class: "text-center"},
elem.H3(nil, elem.Text("Was möchtest du tun?"))),
),
elem.Div(nil,
elem.A(attrs.Props{attrs.Class: "nes-btn", attrs.Href: "/learn"}, elem.Text("Lernen")),
elem.A(attrs.Props{attrs.Class: "nes-btn", attrs.Href: "/play"}, elem.Text("Spielen")),
),
),
elem.Div(attrs.Props{attrs.Class: "container3"},
elem.Span(attrs.Props{attrs.Class: "title"}, elem.Text("Dein Fortschritt")),
elem.Span(nil, elem.Text("Du bist aktuell Leven: ")),
),
),
)
return page
}