Single player game close to finished
This commit is contained in:
parent
3385118b14
commit
584b1c9ef9
12 changed files with 179 additions and 6454 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,6 +1,6 @@
|
|||
**/.*
|
||||
/localbuild.sh
|
||||
/client/build
|
||||
/server/build
|
||||
**/build
|
||||
**/dist
|
||||
/server/priv/static/client.js
|
||||
/server/priv/static/index.html
|
||||
|
|
|
|||
6344
client/dist/client.js
vendored
6344
client/dist/client.js
vendored
File diff suppressed because it is too large
Load diff
17
client/dist/index.html
vendored
17
client/dist/index.html
vendored
|
|
@ -1,17 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
|
||||
<title>
|
||||
client
|
||||
</title>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="/client.js" type="module"></script>
|
||||
</head>
|
||||
<body><div id="app"></div></body>
|
||||
</html>
|
||||
|
|
@ -128,6 +128,7 @@ fn click_cell(
|
|||
])
|
||||
}
|
||||
|
||||
// TODO: merge with shared.click_cell
|
||||
fn room_cell(
|
||||
number: Int,
|
||||
room: Room,
|
||||
|
|
|
|||
|
|
@ -12,3 +12,4 @@ lustre = ">= 5.3.5 and < 6.0.0"
|
|||
gleam_crypto = ">= 1.5.1 and < 2.0.0"
|
||||
group_registry = ">= 1.0.0 and < 2.0.0"
|
||||
wisp = ">= 2.2.1 and < 3.0.0"
|
||||
shared = { path = "../shared" }
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ packages = [
|
|||
{ name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" },
|
||||
{ name = "mist", version = "6.0.2", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "6B03DEEA38A02F276333CB27B53B16D3D45BD741B89599085A601BAF635F2006" },
|
||||
{ name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" },
|
||||
{ name = "shared", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_json", "gleam_stdlib", "lustre"], source = "local", path = "../shared" },
|
||||
{ name = "simplifile", version = "2.4.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "7C18AFA4FED0B4CE1FA5B0B4BAC1FA1744427054EA993565F6F3F82E5453170D" },
|
||||
{ name = "wisp", version = "2.2.2", build_tools = ["gleam"], requirements = ["directories", "exception", "filepath", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "5FF5F1E288C3437252ABB93D8F9CF42FF652CE7AD54480CFE736038DC09C4F22" },
|
||||
]
|
||||
|
|
@ -36,4 +37,5 @@ gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
|||
group_registry = { version = ">= 1.0.0 and < 2.0.0" }
|
||||
lustre = { version = ">= 5.3.5 and < 6.0.0" }
|
||||
mist = { version = ">= 6.0.0 and < 7.0.0" }
|
||||
shared = { path = "../shared" }
|
||||
wisp = { version = ">= 2.2.1 and < 3.0.0" }
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import components.{click_cell}
|
||||
import gleam/erlang/process.{type Subject}
|
||||
import gleam/int
|
||||
import gleam/list
|
||||
|
|
@ -24,6 +25,8 @@ pub fn component() -> lustre.App(
|
|||
pub opaque type Model {
|
||||
Model(
|
||||
state: Msg,
|
||||
players: List(#(String, String)),
|
||||
player_id: Option(String),
|
||||
answers: List(#(Int, #(String, String))),
|
||||
handler: Started(Subject(NotifyServer)),
|
||||
)
|
||||
|
|
@ -48,40 +51,58 @@ fn init(
|
|||
#(a, #(b, ""))
|
||||
})
|
||||
|
||||
#(Model(Initial, initial_array, handler), effect.none())
|
||||
#(
|
||||
Model(
|
||||
Initial,
|
||||
actor.call(handler.data, 1000, message.FetchPlayers),
|
||||
None,
|
||||
initial_array,
|
||||
handler,
|
||||
),
|
||||
effect.none(),
|
||||
)
|
||||
}
|
||||
|
||||
pub opaque type Msg {
|
||||
Initial
|
||||
PickedName(name: String)
|
||||
SharedMessage(message: NotifyClient)
|
||||
ReceiveName(message: String)
|
||||
AcceptName(accept: Option(String))
|
||||
GiveQuestion(name: String, question: String)
|
||||
GiveAnswer(name: String, question: Int, answer: String)
|
||||
PickQuestion
|
||||
PickedQuestion(question: String)
|
||||
GiveAnswer(question: String, answer: String)
|
||||
}
|
||||
|
||||
fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
|
||||
case msg {
|
||||
Initial | SharedMessage(_) -> #(model, effect.none())
|
||||
AcceptName(None) -> #(Model(Initial, [], model.handler), effect.none())
|
||||
AcceptName(Some(name)) -> {
|
||||
#(Model(..model, state: GiveQuestion(name, "")), effect.none())
|
||||
Initial -> #(Model(..model, state: msg), effect.none())
|
||||
PickedName(player_id) -> #(
|
||||
Model(..model, player_id: Some(player_id), state: PickQuestion),
|
||||
effect.none(),
|
||||
)
|
||||
SharedMessage(_) -> #(model, effect.none())
|
||||
AcceptName(None) -> #(
|
||||
Model(Initial, model.players, None, [], model.handler),
|
||||
effect.none(),
|
||||
)
|
||||
AcceptName(Some(player_id)) -> {
|
||||
#(
|
||||
Model(..model, player_id: Some(player_id), state: PickQuestion),
|
||||
effect.none(),
|
||||
)
|
||||
}
|
||||
GiveQuestion(name, question) ->
|
||||
PickQuestion -> #(model, effect.none())
|
||||
PickedQuestion(question) -> {
|
||||
#(Model(..model, state: GiveAnswer(question, "")), effect.none())
|
||||
}
|
||||
GiveAnswer(question, answer) -> {
|
||||
let assert Some(player_id) = model.player_id
|
||||
case int.parse(question) {
|
||||
Ok(question) if question >= 1 && question <= 14 -> #(
|
||||
Model(..model, state: GiveAnswer(name:, question:, answer: "")),
|
||||
effect.none(),
|
||||
)
|
||||
_ -> #(
|
||||
Model(..model, state: GiveQuestion(name:, question: "")),
|
||||
effect.none(),
|
||||
)
|
||||
}
|
||||
GiveAnswer(id, question, answer) -> {
|
||||
Ok(question) -> {
|
||||
actor.send(
|
||||
model.handler.data,
|
||||
message.GiveSingleAnswer(id:, question:, answer:),
|
||||
message.GiveSingleAnswer(id: player_id, question:, answer:),
|
||||
)
|
||||
let new_value = case list.key_find(model.answers, question) {
|
||||
Ok(pair) -> {
|
||||
|
|
@ -93,77 +114,73 @@ fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
|
|||
#(
|
||||
Model(
|
||||
..model,
|
||||
state: GiveQuestion(id, ""),
|
||||
state: PickQuestion,
|
||||
answers: list.key_set(model.answers, question, new_value),
|
||||
),
|
||||
effect.none(),
|
||||
)
|
||||
}
|
||||
_ -> {
|
||||
echo "bad index"
|
||||
#(model, effect.none())
|
||||
}
|
||||
}
|
||||
}
|
||||
ReceiveName(_) -> #(Model(..model, state: msg), effect.none())
|
||||
}
|
||||
}
|
||||
|
||||
fn view(model: Model) -> Element(Msg) {
|
||||
element.fragment([
|
||||
html.div([attribute.class("terminal-prompt")], [
|
||||
case model.state {
|
||||
Initial ->
|
||||
step_prompt(
|
||||
"Hello stranger. To join the quiz, I need to know your name",
|
||||
fn() { view_input(ReceiveName) },
|
||||
)
|
||||
ReceiveName(name) ->
|
||||
step_prompt(
|
||||
"Your name is " <> name <> "? Are you absolutely sure???",
|
||||
fn() { view_yes_no(name, AcceptName) },
|
||||
)
|
||||
GiveQuestion(name, _) ->
|
||||
step_prompt(
|
||||
"Enter the number of the question you want to answer",
|
||||
fn() { view_named_input(name, GiveQuestion) },
|
||||
)
|
||||
GiveAnswer(name, question, _) ->
|
||||
step_prompt(
|
||||
"Enter the answer to question number " <> int.to_string(question),
|
||||
fn() { view_named_keyed_input(question, name, GiveAnswer) },
|
||||
)
|
||||
_ -> html.h3([], [html.text("Waiting for next question")])
|
||||
},
|
||||
]),
|
||||
html.div([class("terminal-header")], [
|
||||
html.div([class("terminal-status")], [
|
||||
html.span([class("status-blink")], [html.text("●")]),
|
||||
html.text(" SYSTEM READY"),
|
||||
html.span([class("ml-8")], [
|
||||
case model.state {
|
||||
Initial -> html.text("STATUS: Please input your name")
|
||||
Initial -> html.text("STATUS: Please select player")
|
||||
ReceiveName(_) -> html.text("STATUS: Please validate your name")
|
||||
GiveQuestion(_, _) -> html.text("STATUS: Pick question to answer")
|
||||
GiveAnswer(_, _, _) -> html.text("STATUS: Give your answer")
|
||||
PickQuestion -> html.text("STATUS: Pick question to answer")
|
||||
GiveAnswer(_, _) -> html.text("STATUS: Give your answer")
|
||||
_ -> html.text("STATUS: Waiting for next question")
|
||||
},
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
terminal_section(model.answers, "[ACTIVE TRANSMISSIONS]", fn(answer) {
|
||||
content_cell(answer)
|
||||
}),
|
||||
html.div([attribute.class("terminal-section")], [
|
||||
html.div([attribute.class("terminal-label mb-4")], [
|
||||
html.text("[ACTIVE TRANSMISSIONS]"),
|
||||
]),
|
||||
]),
|
||||
case model.state {
|
||||
Initial -> view_players(model.players)
|
||||
PickQuestion -> view_questions(model.answers)
|
||||
_ -> content_cell(#(10, #("Answer", "Answer question")))
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
fn terminal_section(
|
||||
answers: List(#(Int, #(String, String))),
|
||||
header: String,
|
||||
extract: fn(#(Int, #(String, String))) -> Element(Msg),
|
||||
) {
|
||||
html.div([attribute.class("terminal-section")], [
|
||||
html.div([attribute.class("terminal-label mb-4")], [
|
||||
html.text(header),
|
||||
]),
|
||||
html.div([attribute.class("singles-grid")], list.map(answers, extract)),
|
||||
fn view_players(players: List(#(String, String))) {
|
||||
html.div([], [
|
||||
html.div(
|
||||
[],
|
||||
list.append(
|
||||
list.index_map(players, fn(item, index) {
|
||||
click_cell(Some(int.to_string(index)), item, PickedName)
|
||||
}),
|
||||
[click_cell(Some("NEW"), #("new", "New Player!"), PickedName)],
|
||||
),
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
fn view_questions(answers: List(#(Int, #(String, String)))) {
|
||||
html.div(
|
||||
[attribute.class("singles-grid")],
|
||||
list.map(answers, fn(content) { content_cell(content) }),
|
||||
)
|
||||
}
|
||||
|
||||
fn content_cell(answer: #(Int, #(String, String))) -> Element(Msg) {
|
||||
let #(question, #(question_text, answer)) = answer
|
||||
html.div(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import shared/message
|
||||
import gleam/int
|
||||
import gleam/json
|
||||
import lustre/attribute.{class}
|
||||
import lustre/element
|
||||
import lustre/element/html.{body, div, head, html, link, meta, script, title}
|
||||
import shared/message.{RoomInfo}
|
||||
import wisp.{type Response}
|
||||
|
||||
pub fn main_html(rooms: List(#(String, message.RoomInfo))) -> Response {
|
||||
|
|
@ -24,19 +24,16 @@ pub fn main_html(rooms: List(#(String, message.RoomInfo))) -> Response {
|
|||
[
|
||||
attribute.id("model"),
|
||||
attribute.type_("application/json"),
|
||||
attribute.src("/client.js"),
|
||||
],
|
||||
|
||||
TODO: CREATE TEXT STRING FROM ROOM LIST!
|
||||
"
|
||||
[
|
||||
{
|
||||
\"id\": \"1234\",
|
||||
\"name\": \"Team A\",
|
||||
\"key\": \"1234\"
|
||||
}
|
||||
]
|
||||
",
|
||||
json.array(rooms, fn(room) {
|
||||
let #(id, RoomInfo(name, pin_enc)) = room
|
||||
json.object([
|
||||
#("id", json.string(id)),
|
||||
#("name", json.string(name)),
|
||||
#("key", json.string(pin_enc)),
|
||||
])
|
||||
})
|
||||
|> json.to_string,
|
||||
),
|
||||
link([
|
||||
attribute.rel("stylesheet"),
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ fn add_player(
|
|||
fn add_room(room_handler: Started(Subject(RoomControl)), json) {
|
||||
let decode_room = {
|
||||
use id <- decode.field("id", decode.string)
|
||||
use pin_enc <- decode.field("pin", decode.string)
|
||||
use pin_enc <- decode.field("pin_enc", decode.string)
|
||||
use name <- decode.field("name", decode.string)
|
||||
decode.success(message.CreateRoom(
|
||||
id:,
|
||||
|
|
|
|||
22
shared/gleam.toml
Normal file
22
shared/gleam.toml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
name = "shared"
|
||||
version = "1.0.0"
|
||||
|
||||
# Fill out these fields if you intend to generate HTML documentation or publish
|
||||
# your project to the Hex package manager.
|
||||
#
|
||||
# description = ""
|
||||
# licences = ["Apache-2.0"]
|
||||
# repository = { type = "github", user = "", repo = "" }
|
||||
# links = [{ title = "Website", href = "" }]
|
||||
#
|
||||
# For a full reference of all the available options, you can have a look at
|
||||
# https://gleam.run/writing-gleam/gleam-toml/.
|
||||
|
||||
[dependencies]
|
||||
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
||||
gleam_json = ">= 3.1.0 and < 4.0.0"
|
||||
gleam_http = ">= 4.3.0 and < 5.0.0"
|
||||
lustre = ">= 5.6.0 and < 6.0.0"
|
||||
|
||||
[dev_dependencies]
|
||||
gleeunit = ">= 1.0.0 and < 2.0.0"
|
||||
20
shared/manifest.toml
Normal file
20
shared/manifest.toml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# This file was generated by Gleam
|
||||
# You typically do not need to edit this file
|
||||
|
||||
packages = [
|
||||
{ name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" },
|
||||
{ name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" },
|
||||
{ name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" },
|
||||
{ name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" },
|
||||
{ name = "gleam_stdlib", version = "0.71.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "702F3BC2A14793906880B1078B19A6165F87323AEE8D0C4A34085846336FCAAE" },
|
||||
{ name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" },
|
||||
{ name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" },
|
||||
{ name = "lustre", version = "5.6.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "EE558CD4DB9F09FCC16417ADF0183A3C2DAC3E4B21ED3AC0CAE859792AB810CA" },
|
||||
]
|
||||
|
||||
[requirements]
|
||||
gleam_http = { version = ">= 4.3.0 and < 5.0.0" }
|
||||
gleam_json = { version = ">= 3.1.0 and < 4.0.0" }
|
||||
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
||||
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
||||
lustre = { version = ">= 5.6.0 and < 6.0.0" }
|
||||
26
shared/src/components.gleam
Normal file
26
shared/src/components.gleam
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import gleam/int
|
||||
import gleam/option.{type Option, None, Some}
|
||||
import lustre/attribute.{class}
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
import lustre/event
|
||||
|
||||
pub fn click_cell(
|
||||
tag: Option(String),
|
||||
id_value_pair: #(String, String),
|
||||
on_click: fn(String) -> msg,
|
||||
) -> Element(msg) {
|
||||
let #(id, value) = id_value_pair
|
||||
html.div([class("participant-login"), event.on_click(on_click(id))], [
|
||||
html.div([class("participant-name")], [
|
||||
html.text(
|
||||
"► "
|
||||
<> case tag {
|
||||
Some(text) -> "[#" <> text <> "] "
|
||||
None -> ""
|
||||
}
|
||||
<> value,
|
||||
),
|
||||
]),
|
||||
])
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue