Skip to content

Commit

Permalink
bye bye forms!
Browse files Browse the repository at this point in the history
  • Loading branch information
chouzar committed Jan 26, 2024
1 parent e8012b7 commit fff5868
Show file tree
Hide file tree
Showing 12 changed files with 490 additions and 501 deletions.
2 changes: 2 additions & 0 deletions gleam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ gleam_stdlib = "~> 0.34"
gleam_erlang = "~> 0.24"
gleam_otp = "~> 0.9"
gleam_http = "~> 3.5"
gleam_json = "~> 1.0"
mist = "~> 0.15"
wisp = "~> 0.10.0"
chip = "~> 0.1.8"
nakai = "~> 0.9.0"
envoy = "~> 1.0"

Expand Down
11 changes: 7 additions & 4 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,31 @@
# You typically do not need to edit this file

packages = [
{ name = "chip", version = "0.1.8", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "chip", source = "hex", outer_checksum = "F7968E064117EC08C40EA7BAC4F36FFCD72E3FC3340558EFC1E843AC628AD2F2" },
{ name = "envoy", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "F9D3AFCF8627E8CBD0FA7296D7187BD024B8DBCF56A152E111A8ECEE27E5E45D" },
{ name = "exception", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "984401CFC95BCA87C391E36194D2B9E5B946467D44893FADB1CA4ACD4B7A29CE" },
{ name = "gleam_crypto", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "DE1FC4E631CA374AB29CCAEAC043EE171B86114D7DC66DD483F0A93BF0C4C6FF" },
{ name = "gleam_erlang", version = "0.24.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "26BDB52E61889F56A291CB34167315780EE4AA20961917314446542C90D1C1A0" },
{ name = "gleam_http", version = "3.5.3", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "C2FC3322203B16F897C1818D9810F5DEFCE347F0751F3B44421E1261277A7373" },
{ name = "gleam_json", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "8B197DD5D578EA6AC2C0D4BDC634C71A5BCA8E7DB5F47091C263ECB411A60DF3" },
{ name = "gleam_json", version = "1.0.0", build_tools = ["gleam"], requirements = ["thoas", "gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "8B197DD5D578EA6AC2C0D4BDC634C71A5BCA8E7DB5F47091C263ECB411A60DF3" },
{ name = "gleam_otp", version = "0.9.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "5FADBBEC5ECF3F8B6BE91101D432758503192AE2ADBAD5602158977341489F71" },
{ name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" },
{ name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" },
{ name = "glisten", version = "0.9.2", build_tools = ["gleam"], requirements = ["gleam_otp", "gleam_stdlib", "gleam_erlang"], otp_app = "glisten", source = "hex", outer_checksum = "C960B6CF25D4AABAB01211146E9B57E11827B9C49E4175217E0FB7EF5BCB0FF7" },
{ name = "glisten", version = "0.10.2", build_tools = ["gleam"], requirements = ["gleam_otp", "gleam_stdlib", "gleam_erlang"], otp_app = "glisten", source = "hex", outer_checksum = "461AE0EC3C2BDCC8B581A0CE07D49597A61226B410A3FE7E237EB924D0D18536" },
{ name = "marceau", version = "1.1.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "1AAD727A30BE0F95562C3403BB9B27C823797AD90037714255EEBF617B1CDA81" },
{ name = "mist", version = "0.15.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_http", "gleam_otp", "gleam_erlang", "glisten"], otp_app = "mist", source = "hex", outer_checksum = "49F51DDB64D7B2832F72727CC9721C478D6B524C96EA444C601A19D01E023C03" },
{ name = "mist", version = "0.17.0", build_tools = ["gleam"], requirements = ["gleam_otp", "gleam_http", "glisten", "gleam_erlang", "gleam_stdlib"], otp_app = "mist", source = "hex", outer_checksum = "DA8ACEE52C1E4892A75181B3166A4876D8CBC69D555E4770250BC84C80F75524" },
{ name = "nakai", version = "0.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "nakai", source = "hex", outer_checksum = "F6FFED9EF4B0E14C7A09B2FB87B42D3A93EFE024FD0299C11F041E92321163A6" },
{ name = "simplifile", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "359CD7006E2F69255025C858CCC6407C11A876EC179E6ED1E46809E8DC6B1AAD" },
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" },
{ name = "wisp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "mist", "gleam_http", "marceau", "simplifile", "exception", "gleam_crypto", "gleam_stdlib", "gleam_json"], otp_app = "wisp", source = "hex", outer_checksum = "744FF91702078301BDF8FE76F26C14B658A7D151D867FA6995762318ED2536A0" },
{ name = "wisp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_http", "exception", "gleam_crypto", "simplifile", "gleam_erlang", "marceau", "gleam_json", "gleam_stdlib", "mist"], otp_app = "wisp", source = "hex", outer_checksum = "744FF91702078301BDF8FE76F26C14B658A7D151D867FA6995762318ED2536A0" },
]

[requirements]
chip = { version = "~> 0.1.8" }
envoy = { version = "~> 1.0" }
gleam_erlang = { version = "~> 0.24" }
gleam_http = { version = "~> 3.5" }
gleam_json = { version = "~> 1.0" }
gleam_otp = { version = "~> 0.9" }
gleam_stdlib = { version = "~> 0.34" }
gleeunit = { version = "~> 1.0" }
Expand Down
16 changes: 7 additions & 9 deletions src/luster.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import luster/store
import luster/web
import luster/web/pages/game
import mist
import gleam/io
import chip
import luster/session

// TODO: Create a web, games and luster/runtime contexts
// TODO: Add a proper supervision tree
Expand All @@ -17,20 +18,17 @@ import gleam/io
// })

pub fn main() -> Nil {
// Grab this secret from somewhere
let assert Ok(store) = store.start()

let selector: process.Selector(x) = process.new_selector()
let assert Ok(registry) = chip.start()
let session = session.Session(store: store, registry: registry)

let request_pipeline = fn(request: request.Request(mist.Connection)) -> response.Response(
mist.ResponseData,
) {
let context = web.Context(store: store, params: [], selector: selector)

web.router(request, context)
web.router(request, session)
}

let assert Ok(Nil) =
let assert Ok(_server) =
mist.new(request_pipeline)
|> mist.port(4444)
|> mist.start_https(
Expand All @@ -45,6 +43,6 @@ pub fn main() -> Nil {
fn env(key: String) -> String {
case envoy.get(key) {
Ok(value) -> value
Error(Nil) -> panic as "unable to find " <> key
Error(Nil) -> panic as "unable to find ENV"
}
}
158 changes: 112 additions & 46 deletions src/luster/events.gleam
Original file line number Diff line number Diff line change
@@ -1,89 +1,155 @@
import gleam/erlang/process
import gleam/otp/actor
import gleam/io
import chip
import gleam/bit_array
import gleam/option.{type Option, Some}
import gleam/erlang/process
import gleam/function
import gleam/http/request.{type Request}
import gleam/http/response.{type Response}
import gleam/io
import gleam/json
import gleam/option.{type Option, Some}
import gleam/otp/actor
import gleam/result.{try}
import luster/session
import luster/store
import luster/web/codec
import mist.{
type Connection, type ResponseData, type WebsocketConnection,
type WebsocketMessage, Binary, Closed, Custom, Shutdown, Text,
}
import luster/web/pages/game
import nakai

pub opaque type Message {
Update
Close
}

pub opaque type State {
State(self: process.Subject(Message))
pub opaque type State(record, message) {
State(
socket: process.Subject(Message),
store: process.Subject(store.Message(record)),
registry: process.Subject(chip.Action(Int, message)),
)
}

pub fn start(request: Request(Connection)) -> Response(ResponseData) {
pub fn start(
request: Request(Connection),
session: session.Session(game.Model, message),
) -> Response(ResponseData) {
mist.websocket(
request: request,
on_init: on_init,
on_init: build_init(session),
on_close: on_close,
handler: handler,
)
}

fn on_init(
_conn: WebsocketConnection,
) -> #(State, Option(process.Selector(Nil))) {
let subject = process.new_subject()

process.send(subject, Update)

#(
State(subject),
Some(
process.new_selector()
|> process.selecting(subject, to_custom),
),
)
}
fn build_init(
session: session.Session(record, message),
) -> fn(WebsocketConnection) ->
#(State(record, message), Option(process.Selector(Message))) {
fn(_conn) {
let subject = process.new_subject()

fn to_custom(message: Message) -> Nil {
case message {
Update -> Nil
#(
State(subject, session.store, session.registry),
Some(
process.new_selector()
|> process.selecting(subject, function.identity),
),
)
}
}

fn on_close(state: State) -> Nil {
io.println("closing connection:")
io.debug(state)
fn on_close(_state: State(record, message)) -> Nil {
io.println("closing connection")
Nil
}

fn handler(
state: State,
state: State(game.Model, message),
conn: WebsocketConnection,
message: WebsocketMessage(Nil),
) -> actor.Next(a, State) {
message: WebsocketMessage(Message),
) -> actor.Next(a, State(game.Model, message)) {
case message {
Text(<<"start: ":utf8, rest:bits>>) -> {
let assert Ok(_) =
mist.send_text_frame(conn, <<"started :":utf8, rest:bits>>)
Binary(bits) -> {
let _ = {
use #(session, blob) <- try(split(bits, 36))
use session <- try(bit_array.to_string(session))
use message <- try(parse_message(blob))
use model <- try(store.one(state.store, session))

let html =
model
|> game.update(message)
|> function.tap(store.update(state.store, session, _))
|> game.view()
|> nakai.to_inline_string()

let _ = mist.send_text_frame(conn, html)
Ok(Nil)
}

actor.continue(state)
}

Custom(Nil) -> {
let assert Ok(_) = mist.send_text_frame(conn, <<"update":utf8>>)
process.send_after(state.self, 1000, Update)
Text(_message) -> {
actor.continue(state)
}

Text(bits) | Binary(bits) -> {
case bit_array.to_string(bits) {
Ok(message) -> io.println("out of bound event: " <> message)
Error(_) -> io.println("out of bound event: malformed bits")
}

actor.continue(state)
Custom(Close) -> {
actor.Stop(process.Normal)
}

Closed | Shutdown -> {
actor.Stop(process.Normal)
}
}
}

fn parse_message(bits: BitArray) -> Result(game.Message, Nil) {
case bits {
<<"\n\n":utf8, "draw-card":utf8, "\n\n":utf8, json:bytes>> -> {
use player <- try(decode(from: json, using: codec.decoder_player))
Ok(game.DrawCard(player))
}

<<"\n\n":utf8, "select-card":utf8, "\n\n":utf8, json:bytes>> -> {
use card <- try(decode(from: json, using: codec.decoder_card))
Ok(game.SelectCard(card))
}

<<"\n\n":utf8, "play-card":utf8, "\n\n":utf8, json:bytes>> -> {
use slot <- try(decode(from: json, using: codec.decoder_slot))
Ok(game.PlayCard(slot))
}

<<"\n\n":utf8, "popup-toggle":utf8, "\n\n":utf8, _json:bytes>> -> {
Ok(game.ToggleScoring)
}

_other -> {
Error(Nil)
}
}
}

fn split(bits: BitArray, at index: Int) -> Result(#(BitArray, BitArray), Nil) {
let size = bit_array.byte_size(bits)

use slice_l <- try(bit_array.slice(bits, 0, index))
use slice_r <- try(bit_array.slice(bits, index, size - index))

Ok(#(slice_l, slice_r))
}

fn decode(from json, using decoder) {
case json.decode_bits(json, decoder) {
Ok(value) -> {
Ok(value)
}

Error(_) -> {
Error(Nil)
}
}
}
63 changes: 27 additions & 36 deletions src/luster/game/cardfield.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ pub fn next(state: GameState, action: Action) -> Result(GameState, Errors) {
True ->
GameState(
..state,
turn: state.turn + 1,
turn: state.turn
+ 1,
sequence: rotate(state.sequence),
phase: Play,
)
Expand Down Expand Up @@ -313,10 +314,9 @@ fn new_line(of piece: piece) -> Line(piece) {

fn new_total_score() -> TotalScore {
TotalScore(
columns: list.map(
slots,
fn(_) { #(Score(0, 0, 0, HighCard), Score(0, 0, 0, HighCard)) },
),
columns: list.map(slots, fn(_) {
#(Score(0, 0, 0, HighCard), Score(0, 0, 0, HighCard))
}),
totals: list.map(slots, fn(_) { figure_player(0) }),
total: #(None, 0),
)
Expand Down Expand Up @@ -470,32 +470,26 @@ fn calculate_total_score(state: GameState) -> TotalScore {

fn calculate_columns(state: GameState) -> List(#(Score, Score)) {
let columns =
list.index_map(
slots,
fn(slot, index) {
let assert Ok(#(s1, s2)) = list.at(state.total_score.columns, index)
list.index_map(slots, fn(slot, index) {
let assert Ok(#(s1, s2)) = list.at(state.total_score.columns, index)

let battle = get(state.board.battleline, slot)
let column_p1 = get(battle, Player1)
let column_p2 = get(battle, Player2)
let battle = get(state.board.battleline, slot)
let column_p1 = get(battle, Player1)
let column_p2 = get(battle, Player2)

#(score(column_p1, s1.bonus_flank), score(column_p2, s2.bonus_flank))
},
)
#(score(column_p1, s1.bonus_flank), score(column_p2, s2.bonus_flank))
})

let flanks = flank_bonuses(columns)

let columns =
list.map(
slots,
fn(slot) {
let battle = get(state.board.battleline, slot)
let column_p1 = get(battle, Player1)
let column_p2 = get(battle, Player2)
list.map(slots, fn(slot) {
let battle = get(state.board.battleline, slot)
let column_p1 = get(battle, Player1)
let column_p2 = get(battle, Player2)

#(score(column_p1, 0), score(column_p2, 0))
},
)
#(score(column_p1, 0), score(column_p2, 0))
})

list.zip(columns, flanks)
|> list.map(fn(scores) {
Expand Down Expand Up @@ -555,18 +549,15 @@ fn flank_bonuses(scores: List(#(Score, Score))) -> List(Option(Player)) {
}

fn calculate_totals(scores: List(#(Score, Score))) -> List(Int) {
list.map(
scores,
fn(score) {
let #(score_p1, score_p2) = score

let score_p1 =
score_p1.score + score_p1.bonus_formation + score_p1.bonus_flank
let score_p2 =
score_p2.score + score_p2.bonus_formation + score_p2.bonus_flank
score_p1 - score_p2
},
)
list.map(scores, fn(score) {
let #(score_p1, score_p2) = score

let score_p1 =
score_p1.score + score_p1.bonus_formation + score_p1.bonus_flank
let score_p2 =
score_p2.score + score_p2.bonus_formation + score_p2.bonus_flank
score_p1 - score_p2
})
}

fn calculate_total(scores: List(Int)) -> Int {
Expand Down
Loading

0 comments on commit fff5868

Please sign in to comment.