feat: add q1 and generator

master
ALI Hamza 2021-12-20 20:00:56 +07:00
parent 38ce009731
commit 9f0c677b6d
Signed by: hamza
GPG Key ID: 22473A32291F8CB6
11 changed files with 313 additions and 405 deletions

@ -0,0 +1,91 @@
# No Time For Directions!
**Hermes** is the Greek god, and amongst others, he is the god of travel, trade, and athletes.
Hermes has placed his hope in you.
You're dropped to a random location near Mount Cyllene in Arcadia, widely considered the birthplace
of Hermes. After years of training, **You** are now set out on a quest. You must steal a key from
**Hecate**, hidden near Mount Cyllene.
Unfortunately, "near", is as close as you know where you are. The instructions on the parchment
Hermes gave begin from here, however, he never had the time to tell you how to follow them, or where
they lead to.
The document has different markings that appear to tell you which direction to travel in. They
indicate a direction, (`N`, `S`, `E` or `W`), and the number of steps you must take to find the
hiding location.
The problem is, that there's over a 100 different directions, and there's no time following these
directions one by one. It will take far too long! You take a moment and work out the final
destination, so you can get more quickly. Given that you can only walk in the cardinal directions,
what is the shortest path to the destination?
### Example
- Given the following input
```
N5
E2
S9
W3
```
Instructs you to to travel `5` steps North, `2` steps East, `9` steps South,
and `3` steps West. Simplifying it, means `4` steps South, and `1` step West,
or `5` steps away.
- Given the following input
```
N6
E5
N5
W3
N4
S9
E4
S1
W6
E3
```
Leaves you `5` steps North, and `3` steps East, or `8` steps away.
Each line will have **at least 2** characters of input, the first being the direction, and
the second being the number of steps. The number of steps on each line will
always be between 1 and 9, inclusive, steps.
**How many steps away** is the key?
{{ if eq .Part 2 }}
**Congratulations! You got Part 1 correct. Your answer was `{{ .Answer1 }}`.**
## Part 2
After some more inspection of the instructions, you decipher the final clue. The final location of
the steps lead you to a different location. However, the first location you visit **twice** is the
location where the key is hidden.
### Example
Given the following input:
```
S7
W9
E4
N4
S3
E5
S1
```
The first location visit twice is `7` blocks away South.
With these new instructions, to find the key, **how many steps away is the first location you visit
twice?**
{{ end }}

@ -1,67 +0,0 @@
# No Time For Directions!
__Hermes__ is the Greek god, and amongst others, he is the god of travel, trade,
and athletes. Hermes has placed his hope in you.
You're dropped to a random location near Mount Cyllene in Arcadia, widely
considered the birthplace of Hermes. After years of training, **You** are now
set out on a quest. You must steal a key from __Hecate__, hidden near Mount
Cyllene.
Unfortunately, "near", is as close as you know where you are. The instructions
on the parchment Hermes gave begin from here, however, he never had the time to
tell you how to follow them, or where they lead to.
The document has different markings that appear to tell you which direction to
travel in. They indicade a direction, (`N`, `S`, `E` or `W`), and the number of
steps you must take to find the hiding location.
The problem is, that there's over a 100 different directions, and there's no
time following these directions one by one. It will take far too long! You take
a moment and work out the final destination, so you can get more quickly. Given
that you can only walk in the cardinal directions, what is the shortest path to
the destination?
### Example
- Given the following input
```
N5
E2
S9
W3
```
Instructs you to to travel `5` steps North, `2` steps East, `9` steps South,
and `3` steps West. Simplifying it, means `4` steps South, and `1` step West,
or `5` steps away.
- Given the following input
```
N6
E5
N5
W3
N4
S9
E4
S1
W6
E3
```
Leaves you `5` steps North, and `3` steps East, or `8` steps away.
Each line will have 2 characters of input, the first being the direction, and
the second being the number of steps. The number of steps on each line will
always be between 1 and 9, inclusive, steps.
**How many steps away** is the key?
{{ if ge .Step 2 }}
## Part 2
{{ end }}

@ -0,0 +1,150 @@
package question
import (
_ "embed"
"fmt"
"math"
"math/rand"
"strconv"
"strings"
"github.com/hhhapz/codequest/models"
)
func move(x, y int, dir byte, step int) (int, int) {
switch dir {
case 'N':
return x, y + step
case 'S':
return x, y - step
case 'E':
return x + step, y
case 'W':
return x - step, y
}
return x, y
}
func q1P1(steps []string) (x int, y int) {
for _, step := range steps {
dir := step[0]
amt, _ := strconv.Atoi(step[1:])
x, y = move(x, y, dir, amt)
}
return
}
func q1P2(steps []string) (x int, y int) {
known := make(map[int]map[int]bool)
for _, step := range steps {
dir := step[0]
amt, _ := strconv.Atoi(step[1:])
x, y = move(x, y, dir, amt)
if known[x] == nil {
known[x] = make(map[int]bool)
}
if known[x][y] {
return
}
known[x][y] = true
}
return
}
func q1Total(a, b int) int {
if a < 0 {
a = -a
}
if b < 0 {
b = -b
}
return a + b
}
func init() {
const directions = "NSWE"
var q *Question
q = &Question{
ID: "directions",
Text: q01Text,
Level: Level1,
Generate: func(u *models.User) string {
res := make([]string, 0, 100)
r := userRandom(u)
known := make(map[int]map[int]bool)
var x, y int
for i := 0; i < 100; i++ {
dir := directions[r.Intn(4)]
steps := r.Intn(30) + 1
newX, newY := move(x, y, dir, steps)
if known[newX] == nil {
known[newX] = make(map[int]bool)
}
if known[newX][newY] {
i--
continue
}
known[newX][newY] = true
x, y = newX, newY
res = append(res, fmt.Sprintf("%c%d", dir, steps))
}
n := rand.Intn(20) + 10
locX, locY := q1P1(res[:n])
dup := rand.Intn(20) + 35
lastX, lastY := q1P1(res[:dup])
fmt.Println(locX, locY)
fmt.Println(lastX, lastY)
fmt.Println(dup)
dX := math.Max(float64(locX), float64(lastX)) - math.Min(float64(locX), float64(lastX))
if locX > lastX {
res[dup] = fmt.Sprintf("E%d", int(dX))
dup++
} else if locX < lastX {
res[dup] = fmt.Sprintf("W%d", int(dX))
dup++
}
fmt.Println(res[dup-1])
fmt.Println("!!")
if locY > lastY {
res[dup] = fmt.Sprintf("N%d", int(math.Max(float64(locY), float64(lastY))-math.Min(float64(locY), float64(lastY))))
} else if locY < lastY {
res[dup] = fmt.Sprintf("S%d", int(math.Max(float64(locY), float64(lastY))-math.Min(float64(locY), float64(lastY))))
}
return strings.Join(res, "\n")
},
Validate: func(u *models.User, level Part, input string) bool {
inp := q.Generate(u)
lastX, lastY := q1P1(strings.Split(inp, "\n"))
if level == Part1 {
total := q1Total(lastX, lastY)
return strconv.Itoa(total) == input
}
lastX, lastY = q1P2(strings.Split(inp, "\n"))
if level == Part2 {
total := q1Total(lastX, lastY)
return strconv.Itoa(total) == input
}
return false
},
}
Register(q)
}
//go:embed prompts/q01.md
var q01Text string

@ -0,0 +1,29 @@
package question
import (
"fmt"
"strings"
"testing"
"github.com/hhhapz/codequest/models"
)
func TestQ01(t *testing.T) {
u := &models.User{
ID: "0",
}
q := QuestionByID("directions")
input := q.Generate(u)
fmt.Println("STAAART")
x, y := q1P1(strings.Split(input, "\n"))
fmt.Println("STAAART")
t.Logf("SOLUTION (P1): (%d, %d): %d", x, y, q1Total(x, y))
x, y = q1P2(strings.Split(input, "\n"))
t.Logf("SOLUTION (P2): (%d, %d): %d", x, y, q1Total(x, y))
fmt.Printf("INPUT: %v", input)
}

@ -0,0 +1,11 @@
package question
func init() {
Register(&Question{
ID: "",
Text: "",
Level: 0,
Generate: nil,
Validate: nil,
})
}

@ -0,0 +1 @@
package question

@ -1,13 +0,0 @@
package question
import "github.com/hhhapz/codequest/models"
func init() {
Register(&Question{
ID: "directions",
Text: ``,
Level: 0,
Generate: func(*models.User) string { panic("not implemented") },
Validate: func(*models.User, string) bool { panic("not implemented") },
})
}

@ -1,13 +0,0 @@
package question
import "github.com/hhhapz/codequest/models"
func init() {
Register(&Question{
ID: "",
Text: "",
Level: 0,
Generate: func(*models.User) string { panic("not implemented") },
Validate: func(*models.User, string) bool { panic("not implemented") },
})
}

@ -2,6 +2,7 @@ package question
import (
"math/rand"
"strconv"
"github.com/hhhapz/codequest/models"
)
@ -13,21 +14,24 @@ var bank Bank
//
// A custom type was created for convenience for picking random questions based
// on difficulties, and for registration.
type Bank [][]*Question
type Bank []*Question
func Register(q *Question) {
bank[q.Level] = append(bank[q.Level], q)
bank = append(bank, q)
}
func Questions(user *models.User, level Level) []*Question {
qs := make([]*Question, len(bank))
r := rand.New(rand.NewSource(user.CreatedAt.Time().Unix()))
for level := range bank {
idx := r.Intn(len(bank[level]))
qs[level] = bank[level][idx]
func QuestionByID(id string) *Question {
for _, q := range bank {
if q.ID == id {
return q
}
}
return nil
}
return qs
func Questions(user *models.User, level Level) []*Question {
// TODO: player skill level should be used to determine which problems to return
return bank
}
type Question struct {
@ -36,16 +40,29 @@ type Question struct {
Level Level
Generate func(user *models.User) string
Validate func(user *models.User, solution string) bool
Validate func(user *models.User, part Part, solution string) bool
}
// Level represents the difficulty of each question.
// As the level gets higher, the difficulty also gets higher.
type Level int
// Allowed difficulty levels.
const (
Level1 Level = iota
Level2
Level3
)
type Part int
const (
Part1 Part = iota
Part2
)
func userRandom(u *models.User) *rand.Rand {
id := u.ID
if len(id) > 17 {
id = id[:17]
}
seed, _ := strconv.ParseInt(id, 10, 64)
return rand.New(rand.NewSource(seed))
}

@ -1,298 +0,0 @@
openapi: "3.0.0"
info:
version: 0.1.0
title: Swagger Hackathon
description: A hackathon, hosted in JIS API specification created by Hamza Ali.
contact:
name: Hamza Ali
email: me@hamzantal.pw
license:
name: MIT License
url: "https://hamza.mit-license.org/"
paths:
/auth/code:
get:
description: Generate oauth exchange url.
tags: ["Auth"]
operationId: gen oauth
parameters:
- name: callback
in: query
required: true
schema:
type: string
format: uri
responses:
'200':
description: OAuth Consent Page URI.
content:
application/json:
schema:
$ref: "#/components/schemas/ConsentPage"
default:
$ref: "#/components/responses/DefaultResponse"
/auth/authorize:
get:
description: Authorization response callback location.
tags: ["Auth"]
operationId: authorize callback
parameters:
- name: state
in: query
required: true
schema:
type: string
- name: code
in: query
required: true
schema:
type: string
responses:
'302':
description: Redirect to webpage.
headers:
Location:
schema:
type: string
format: uri
default:
$ref: "#/components/responses/DefaultResponse"
/auth/token:
delete:
tags: ["Auth"]
operationId: delete token
x-go-middlewares: ["token"]
parameters:
- $ref: "#/components/parameters/Token"
- name: all
in: query
required: true
schema:
type: boolean
responses:
'204':
description: User successfully logged out.
default:
$ref: "#/components/responses/DefaultResponse"
/users/me:
get:
description: Get self user information.
tags: ["Users"]
operationId: get me
x-go-middlewares: ["token"]
parameters:
- $ref: "#/components/parameters/Token"
responses:
'200':
description: User information.
content:
application/json:
schema:
$ref: "#/components/schemas/User"
default:
$ref: "#/components/responses/DefaultResponse"
put:
description: Update self user.
tags: ["Users"]
operationId: modify user
x-go-middlewares: ["token"]
parameters:
- $ref: "#/components/parameters/Token"
requestBody:
description: Modified user information.
content:
application/json:
schema:
type: object
required:
- name
- grade_level
properties:
name:
type: string
grade_level:
type: integer
responses:
'200':
description: New user data
content:
application/json:
schema:
$ref: "#/components/schemas/User"
default:
$ref: "#/components/responses/DefaultResponse"
/users/email:
get:
description: |-
Get user info by email.
Requires admin to get user info not equal to the owner of the token.
tags: ["Users"]
operationId: get user by email
x-go-middlewares: ["token"]
parameters:
- $ref: "#/components/parameters/Token"
- name: email
in: query
description: User email.
required: true
schema:
type: string
format: email
responses:
'200':
description: User information.
content:
application/json:
schema:
$ref: "#/components/schemas/User"
default:
$ref: "#/components/responses/DefaultResponse"
put:
description: Update another user. Requires admin.
tags: ["Users"]
operationId: modify other user
x-go-middlewares: ["token", "admin_token"]
parameters:
- name: token
in: cookie
description: User authentication token.
required: true
schema:
type: string
- name: email
in: query
description: User email.
required: true
schema:
type: string
format: email
requestBody:
description: Modified user information.
content:
application/json:
schema:
type: object
required:
- name
- email
- picture
- grade_level
- teacher
- admin
properties:
name:
type: string
new_email:
type: string
format: email
picture:
type: string
format: uri
grade_level:
type: integer
teacher:
type: boolean
admin:
type: boolean
responses:
'200':
description: User information.
content:
application/json:
schema:
$ref: "#/components/schemas/User"
default:
$ref: "#/components/responses/DefaultResponse"
/users/all:
get:
description: Get all users. Requires admin.
tags: ["Users"]
operationId: get all users
x-go-middlewares: ["token", "admin_token"]
parameters:
- $ref: "#/components/parameters/Token"
responses:
'200':
description: All user information.
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
default:
$ref: "#/components/responses/DefaultResponse"
components:
parameters:
Token:
name: token
in: cookie
description: User authentication token.
required: true
schema:
type: string
responses:
DefaultResponse:
description: Unexpected server error or invalid user input.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
ConsentPage:
type: object
required:
- url
properties:
url:
type: string
format: uri
User:
type: object
required:
- id
- name
- email
- picture
- teacher
- admin
- created_at
properties:
id:
type: string
name:
type: string
email:
type: string
format: email
picture:
type: string
format: uri
grade_level:
type: integer
description: GradeLevel is only present if teacher is false.
teacher:
type: boolean
admin:
type: boolean
created_at:
type: string
format: date-time
Error:
type: object
required:
- message
properties:
message:
type: string