Compare commits
18 Commits
9c5b17261f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dc8871bb8 | ||
|
|
74ea6270c8 | ||
|
|
a1d2669dad | ||
|
|
ad286d745f | ||
|
|
9c8b8177ad | ||
|
|
1e0a4c6317 | ||
|
|
748c2de536 | ||
|
|
9ea2158def | ||
|
|
18e9322013 | ||
|
|
04f84e34c0 | ||
|
|
dec1323c47 | ||
|
|
3f2f19943a | ||
|
|
9d6700fa9a | ||
|
|
5f63d7385d | ||
|
|
885aefc96c | ||
|
|
64fa44e45e | ||
|
|
9ded6a4615 | ||
|
|
d2bec96645 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -30,3 +30,6 @@ go.work.sum
|
|||||||
# Editor/IDE
|
# Editor/IDE
|
||||||
# .idea/
|
# .idea/
|
||||||
# .vscode/
|
# .vscode/
|
||||||
|
|
||||||
|
.venv
|
||||||
|
__pycache__
|
||||||
|
|||||||
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"petname"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
# hangman
|
# hangman
|
||||||
|
|
||||||
|
- [ ] clean up https://gitea.elara.ws/Hazel/go-words as actual library
|
||||||
17
go.mod
17
go.mod
@@ -1,3 +1,20 @@
|
|||||||
module gitea.elara.ws/Hazel/hangman
|
module gitea.elara.ws/Hazel/hangman
|
||||||
|
|
||||||
go 1.24.2
|
go 1.24.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
gitea.elara.ws/Hazel/go-words v0.0.0-20250701093631-6125867cea5a // indirect
|
||||||
|
gitea.elara.ws/Hazel/words v1.0.5 // indirect
|
||||||
|
github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
|
github.com/labstack/echo/v4 v4.13.4 // indirect
|
||||||
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.14 // 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.38.0 // indirect
|
||||||
|
golang.org/x/net v0.40.0 // indirect
|
||||||
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
|
golang.org/x/text v0.25.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
31
go.sum
Normal file
31
go.sum
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
gitea.elara.ws/Hazel/go-words v0.0.0-20250701093631-6125867cea5a h1:E5BgJBsBKL1MNW1W6HK69XYXvDKtYLfRezl1nh27B5g=
|
||||||
|
gitea.elara.ws/Hazel/go-words v0.0.0-20250701093631-6125867cea5a/go.mod h1:8vVkpfa+5Xcte2f7YaH6ao8kVZlbz1ErCVGqNanaNLA=
|
||||||
|
gitea.elara.ws/Hazel/words v1.0.4 h1:pc7RZ0gNYMZ/zeYmY7FZrscZZOO4TEQt5d6SsXLe4zc=
|
||||||
|
gitea.elara.ws/Hazel/words v1.0.4/go.mod h1:IwQ+eZpY2Kr02RPYpyFDjHNJPPbsf1NOvgc4ZMeg2zg=
|
||||||
|
gitea.elara.ws/Hazel/words v1.0.5 h1:FjpQezXDPxgNing/DAMJStLT3TC7/dxvCQj3Cg3AJB4=
|
||||||
|
gitea.elara.ws/Hazel/words v1.0.5/go.mod h1:IwQ+eZpY2Kr02RPYpyFDjHNJPPbsf1NOvgc4ZMeg2zg=
|
||||||
|
github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0 h1:aYo8nnk3ojoQkP5iErif5Xxv0Mo0Ga/FR5+ffl/7+Nk=
|
||||||
|
github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0/go.mod h1:8AuBTZBRSFqEYBPYULd+NN474/zZBLP+6WeT5S9xlAc=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
|
||||||
|
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
|
||||||
|
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.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
|
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/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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
|
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||||
|
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
127
internal/game/session.go
Normal file
127
internal/game/session.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
petname "github.com/dustinkirkland/golang-petname"
|
||||||
|
)
|
||||||
|
|
||||||
|
var lastSessionId int = 0
|
||||||
|
var sessionStorage []Session = []Session{}
|
||||||
|
var nameToSession map[string]*Session = make(map[string]*Session)
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
id int
|
||||||
|
Name string
|
||||||
|
Users []User
|
||||||
|
|
||||||
|
phrase []string
|
||||||
|
AskedLetters []string
|
||||||
|
DiscoveredPhrase []string
|
||||||
|
Mistakes int
|
||||||
|
|
||||||
|
userIndex int
|
||||||
|
CurrentUser *User
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSession(phrase string) *Session {
|
||||||
|
sessionName := petname.Generate(3, "-")
|
||||||
|
p := strings.Split(phrase, "")
|
||||||
|
|
||||||
|
s := Session{
|
||||||
|
id: lastSessionId,
|
||||||
|
Name: sessionName,
|
||||||
|
Users: make([]User, 0),
|
||||||
|
phrase: p,
|
||||||
|
AskedLetters: []string{},
|
||||||
|
DiscoveredPhrase: make([]string, len(p)),
|
||||||
|
Mistakes: 0,
|
||||||
|
userIndex: 0,
|
||||||
|
CurrentUser: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionStorage = append(sessionStorage, s)
|
||||||
|
nameToSession[sessionName] = &sessionStorage[len(sessionStorage)-1]
|
||||||
|
lastSessionId++
|
||||||
|
|
||||||
|
return &sessionStorage[len(sessionStorage)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSession(name string) (*Session, error) {
|
||||||
|
s, ok := nameToSession[name]
|
||||||
|
if !ok {
|
||||||
|
return s, errors.New("can't find session " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) AddUser(user User) *User {
|
||||||
|
s.Users = append(s.Users, user)
|
||||||
|
|
||||||
|
// fmt.Printf("#### Adding %v:\t%p\n", s.Users[len(s.Users)-1].Name, &(s.Users[len(s.Users)-1]))
|
||||||
|
// append changes the pointers to the users because it needs to resize that slice
|
||||||
|
s.CurrentUser = &(s.Users[s.userIndex])
|
||||||
|
return &(s.Users[len(s.Users)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) VerifySignature(signature string, message []byte) (*User, error) {
|
||||||
|
sig, err := base64.StdEncoding.DecodeString(signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range s.Users {
|
||||||
|
// fmt.Printf("looking %v:\t%p\n", s.Users[i].Name, &(s.Users[i]))
|
||||||
|
|
||||||
|
if ed25519.Verify(s.Users[i].PublicKey, message, sig) {
|
||||||
|
return &s.Users[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("this user was not fount in the current session")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) GuessLetter(letter string) (*Session, error) {
|
||||||
|
letter = strings.ToLower(letter)
|
||||||
|
if len(letter) != 1 {
|
||||||
|
return s, errors.New("the letter needs to have a length of one")
|
||||||
|
}
|
||||||
|
for _, asked := range s.AskedLetters {
|
||||||
|
if letter == asked {
|
||||||
|
return s, errors.New("the letter " + letter + " was already asked")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.AskedLetters = append(s.AskedLetters, letter)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for i, l := range s.phrase {
|
||||||
|
if l == letter {
|
||||||
|
found = true
|
||||||
|
s.DiscoveredPhrase[i] = s.phrase[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
s.Mistakes++
|
||||||
|
}
|
||||||
|
|
||||||
|
s.userIndex = (s.userIndex + 1) % len(s.Users)
|
||||||
|
s.CurrentUser = &s.Users[s.userIndex]
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) GetUserByName(name string) (*User, error) {
|
||||||
|
for _, u := range s.Users {
|
||||||
|
if u.Name == name {
|
||||||
|
return &u, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("nu user with the name " + name + " found in " + s.Name)
|
||||||
|
}
|
||||||
10
internal/game/user.go
Normal file
10
internal/game/user.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
PublicKey ed25519.PublicKey
|
||||||
|
}
|
||||||
14
internal/rest_handler/create_session.go
Normal file
14
internal/rest_handler/create_session.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package rest_handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitea.elara.ws/Hazel/hangman/internal/game"
|
||||||
|
"gitea.elara.ws/Hazel/words"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateSession(c echo.Context) error {
|
||||||
|
s := game.NewSession(words.Words.GetRandomWord())
|
||||||
|
return c.JSON(http.StatusOK, s)
|
||||||
|
}
|
||||||
42
internal/rest_handler/create_user.go
Normal file
42
internal/rest_handler/create_user.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package rest_handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitea.elara.ws/Hazel/hangman/internal/game"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateUser(c echo.Context) error {
|
||||||
|
session, err := game.GetSession(c.Param("session"))
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
type BodyContent struct {
|
||||||
|
Name string
|
||||||
|
PublicKey string
|
||||||
|
}
|
||||||
|
var bodyContent BodyContent
|
||||||
|
|
||||||
|
err = c.Bind(&bodyContent)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, err := base64.StdEncoding.DecodeString(bodyContent.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
user := session.AddUser(game.User{
|
||||||
|
Name: bodyContent.Name,
|
||||||
|
PublicKey: pub,
|
||||||
|
})
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, ResponseData{
|
||||||
|
Session: session,
|
||||||
|
User: user,
|
||||||
|
})
|
||||||
|
}
|
||||||
39
internal/rest_handler/guess_letter.go
Normal file
39
internal/rest_handler/guess_letter.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package rest_handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GuessLetter(c echo.Context) error {
|
||||||
|
session, user, err := GetData(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if session.CurrentUser != user {
|
||||||
|
return c.String(http.StatusBadRequest, "It's not the turn of user "+user.Name+". It's the turn of "+session.CurrentUser.Name+".")
|
||||||
|
}
|
||||||
|
|
||||||
|
type BodyContent struct {
|
||||||
|
Guess string
|
||||||
|
}
|
||||||
|
var bodyContent BodyContent
|
||||||
|
|
||||||
|
err = c.Bind(&bodyContent)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = session.GuessLetter(bodyContent.Guess)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, ResponseData{
|
||||||
|
User: user,
|
||||||
|
Session: session,
|
||||||
|
})
|
||||||
|
}
|
||||||
35
internal/rest_handler/helper.go
Normal file
35
internal/rest_handler/helper.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package rest_handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"gitea.elara.ws/Hazel/hangman/internal/game"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResponseData struct{
|
||||||
|
Session *game.Session
|
||||||
|
User *game.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetData(c echo.Context) (*game.Session, *game.User, error) {
|
||||||
|
session, err := game.GetSession(c.Param("session"))
|
||||||
|
if err != nil {
|
||||||
|
return session, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := c.Request().Header.Get("signature")
|
||||||
|
body, _ := io.ReadAll(c.Request().Body)
|
||||||
|
|
||||||
|
user, err := session.VerifySignature(sig, body)
|
||||||
|
if err != nil {
|
||||||
|
return session, user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Request().Body = io.NopCloser(bytes.NewBuffer(body))
|
||||||
|
|
||||||
|
// fmt.Printf("user %v:\t%p\n", user.Name, user)
|
||||||
|
|
||||||
|
return session, user, nil
|
||||||
|
}
|
||||||
24
internal/rest_handler/test_auth.go
Normal file
24
internal/rest_handler/test_auth.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package rest_handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitea.elara.ws/Hazel/hangman/internal/game"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuth(c echo.Context) error {
|
||||||
|
type TestResults struct {
|
||||||
|
Session *game.Session
|
||||||
|
User *game.User
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
session, user, err := GetData(c)
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, TestResults{
|
||||||
|
Session: session,
|
||||||
|
User: user,
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
|
}
|
||||||
13
internal/view_handler/create_session.go
Normal file
13
internal/view_handler/create_session.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package view_handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateSession(c echo.Context) error {
|
||||||
|
return c.Render(http.StatusOK, "create_session", map[string]interface{}{
|
||||||
|
"name": "create session",
|
||||||
|
})
|
||||||
|
}
|
||||||
13
internal/view_handler/create_user.go
Normal file
13
internal/view_handler/create_user.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package view_handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateUser(c echo.Context) error {
|
||||||
|
return c.Render(http.StatusOK, "create_user", map[string]interface{}{
|
||||||
|
"name": c.Param("name"),
|
||||||
|
})
|
||||||
|
}
|
||||||
25
main.go
25
main.go
@@ -1,7 +1,28 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.elara.ws/Hazel/hangman/internal/rest_handler"
|
||||||
|
"gitea.elara.ws/Hazel/hangman/internal/view_handler"
|
||||||
|
"gitea.elara.ws/Hazel/hangman/internal/websocket_handler"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println("wanna play hangman? Well ya cant since it isn't implemented yet.")
|
fmt.Println("wanna play hangman? Well ya cant since it isn't implemented yet..")
|
||||||
|
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
e.POST("/api/session", rest_handler.CreateSession)
|
||||||
|
e.POST("/api/:session/user", rest_handler.CreateUser)
|
||||||
|
e.POST("/api/:session/test-auth", rest_handler.TestAuth)
|
||||||
|
e.POST("/api/:session/guess", rest_handler.GuessLetter)
|
||||||
|
|
||||||
|
e.GET("/ws/:session/:user", websocket_handler.UserWS)
|
||||||
|
|
||||||
|
e.GET("/", view_handler.CreateSession)
|
||||||
|
e.GET("/:name", view_handler.CreateUser)
|
||||||
|
|
||||||
|
e.Logger.Fatal(e.Start(":1323"))
|
||||||
}
|
}
|
||||||
|
|||||||
120
python_frontend/frontend.py
Normal file
120
python_frontend/frontend.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
import requests
|
||||||
|
from nacl.signing import SigningKey, VerifyKey
|
||||||
|
import base64
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
BASE_URL = "http://localhost:1323/api"
|
||||||
|
|
||||||
|
|
||||||
|
class User:
|
||||||
|
def __init__(self, session: Session, name: str) -> None:
|
||||||
|
self.session = session
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
self.signing_key = SigningKey.generate()
|
||||||
|
|
||||||
|
r = requests.post(BASE_URL + f"/{self.session.name}/user", json={
|
||||||
|
"Name": name,
|
||||||
|
"PublicKey": base64.b64encode(self.signing_key.verify_key.__bytes__()).decode("ascii")
|
||||||
|
})
|
||||||
|
|
||||||
|
data = r.json()
|
||||||
|
print(json.dumps(data, indent=4))
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"User({self.name})"
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth(self):
|
||||||
|
r = self.signed_request("/test-auth", {
|
||||||
|
"foo": "bar"
|
||||||
|
})
|
||||||
|
print(r.content)
|
||||||
|
|
||||||
|
def guess(self, letter: str):
|
||||||
|
r = self.signed_request("/guess", {
|
||||||
|
"Guess": letter
|
||||||
|
})
|
||||||
|
print(r.content)
|
||||||
|
|
||||||
|
def signed_request(self, endpoint: str, body: dict) -> requests.Response:
|
||||||
|
payload = json.dumps(body).encode("utf-8")
|
||||||
|
|
||||||
|
signature = self.signing_key.sign(payload)
|
||||||
|
|
||||||
|
return requests.post(
|
||||||
|
url=BASE_URL+f"/{self.session.name}"+endpoint,
|
||||||
|
data=payload,
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"signature": base64.b64encode(signature.signature).decode("ascii")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Game:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.session_name = input("session: ").strip().lower()
|
||||||
|
|
||||||
|
if self.session_name == "":
|
||||||
|
data = requests.post(BASE_URL + "/session").json()
|
||||||
|
self.session_name = data["Name"]
|
||||||
|
|
||||||
|
print(f"playing with session {self.session_name}")
|
||||||
|
|
||||||
|
self.user_name = input("name: ").strip()
|
||||||
|
|
||||||
|
self.signing_key = SigningKey.generate()
|
||||||
|
r = requests.post(BASE_URL + f"/{self.session_name}/user", json={
|
||||||
|
"Name": self.user_name,
|
||||||
|
"PublicKey": base64.b64encode(self.signing_key.verify_key.__bytes__()).decode("ascii")
|
||||||
|
})
|
||||||
|
|
||||||
|
self.last_turn = r.json()
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Game({self.session_name}, {self.user_name})"
|
||||||
|
|
||||||
|
def print_turn(self, data: dict):
|
||||||
|
print()
|
||||||
|
print(" ".join(c if c != '' else '_' for c in data["Session"]["DiscoveredPhrase"]))
|
||||||
|
print(f"guessed: {','.join(data['Session']['AskedLetters'])}")
|
||||||
|
|
||||||
|
def guess(self):
|
||||||
|
self.print_turn(self.last_turn)
|
||||||
|
self.send_guess(input(f"{self.user_name}: ").strip())
|
||||||
|
|
||||||
|
def send_guess(self, letter: str):
|
||||||
|
data = self.signed_request("/guess", {
|
||||||
|
"Guess": letter
|
||||||
|
}).json()
|
||||||
|
|
||||||
|
self.last_turn = data
|
||||||
|
|
||||||
|
def signed_request(self, endpoint: str, body: dict) -> requests.Response:
|
||||||
|
payload = json.dumps(body).encode("utf-8")
|
||||||
|
|
||||||
|
signature = self.signing_key.sign(payload)
|
||||||
|
|
||||||
|
return requests.post(
|
||||||
|
url=BASE_URL+f"/{self.session_name}"+endpoint,
|
||||||
|
data=payload,
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"signature": base64.b64encode(signature.signature).decode("ascii")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
g = Game()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
g.guess()
|
||||||
108
python_frontend/test.py
Normal file
108
python_frontend/test.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
import requests
|
||||||
|
from nacl.signing import SigningKey, VerifyKey
|
||||||
|
import base64
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
BASE_URL = "http://localhost:1323/api"
|
||||||
|
|
||||||
|
|
||||||
|
class User:
|
||||||
|
def __init__(self, session: Session, name: str) -> None:
|
||||||
|
self.session = session
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
self.signing_key = SigningKey.generate()
|
||||||
|
|
||||||
|
r = requests.post(BASE_URL + f"/{self.session.name}/user", json={
|
||||||
|
"Name": name,
|
||||||
|
"PublicKey": base64.b64encode(self.signing_key.verify_key.__bytes__()).decode("ascii")
|
||||||
|
})
|
||||||
|
|
||||||
|
data = r.json()
|
||||||
|
print(json.dumps(data, indent=4))
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"User({self.name})"
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth(self):
|
||||||
|
r = self.signed_request("/test-auth", {
|
||||||
|
"foo": "bar"
|
||||||
|
})
|
||||||
|
print(r.content)
|
||||||
|
|
||||||
|
def guess(self, letter: str):
|
||||||
|
r = self.signed_request("/guess", {
|
||||||
|
"Guess": letter
|
||||||
|
})
|
||||||
|
print(r.content)
|
||||||
|
|
||||||
|
def signed_request(self, endpoint: str, body: dict) -> requests.Response:
|
||||||
|
payload = json.dumps(body).encode("utf-8")
|
||||||
|
|
||||||
|
signature = self.signing_key.sign(payload)
|
||||||
|
|
||||||
|
return requests.post(
|
||||||
|
url=BASE_URL+f"/{self.session.name}"+endpoint,
|
||||||
|
data=payload,
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"signature": base64.b64encode(signature.signature).decode("ascii")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Session:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
data = requests.post(BASE_URL + "/session").json()
|
||||||
|
self.name = data["Name"]
|
||||||
|
self.users: List[User] = []
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Session({self.name})"
|
||||||
|
|
||||||
|
def add_user(self, name: str) -> User:
|
||||||
|
u = User(session=self, name=name)
|
||||||
|
self.users.append(u)
|
||||||
|
return u
|
||||||
|
|
||||||
|
|
||||||
|
def build_word_dict():
|
||||||
|
lines = [
|
||||||
|
"""package words
|
||||||
|
|
||||||
|
var Words []string = []string{"""
|
||||||
|
]
|
||||||
|
|
||||||
|
with open("/usr/share/dict/words", "r") as f:
|
||||||
|
for l in f.readlines():
|
||||||
|
l = l.strip()
|
||||||
|
if not l.isalpha():
|
||||||
|
continue
|
||||||
|
lines.append(f'\t"{l}",')
|
||||||
|
|
||||||
|
lines.append("}")
|
||||||
|
|
||||||
|
with open("internal/words/dictionary.go", "w") as f:
|
||||||
|
f.write("\n".join(lines))
|
||||||
|
|
||||||
|
exit()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
s = Session()
|
||||||
|
print(s)
|
||||||
|
a = s.add_user(name="Hazel_1")
|
||||||
|
b = s.add_user(name="Hazel_2")
|
||||||
|
c = s.add_user(name="Hazel_3")
|
||||||
|
|
||||||
|
a.guess("a")
|
||||||
|
b.guess("e")
|
||||||
|
c.guess("i")
|
||||||
|
a.guess("o")
|
||||||
|
b.guess("u")
|
||||||
16
templates/base.html
Normal file
16
templates/base.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{{template "title" .}}</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css">
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/anchor@3.x.x/dist/cdn.min.js"></script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container mt-5">
|
||||||
|
{{template "body" .}}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
25
templates/create_session.html
Normal file
25
templates/create_session.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{{define "title"}}
|
||||||
|
create or join session | {{index . "name"}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "body"}}
|
||||||
|
<button class="button is-primary" id="createSessionBtn">create session</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('createSessionBtn').addEventListener('click', async function() {
|
||||||
|
const response = await fetch('/api/session', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
window.open(`/${data.Name}`);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
7
templates/create_user.html
Normal file
7
templates/create_user.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{{define "title"}}
|
||||||
|
create user | {{index . "name"}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "body"}}
|
||||||
|
<h1>Current Session: {{index . "name"}}</h1>
|
||||||
|
{{end}}
|
||||||
Reference in New Issue
Block a user