-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
490 additions
and
501 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.