basic buzzer working with websocket comms
This commit is contained in:
		
							parent
							
								
									0fd244f3d4
								
							
						
					
					
						commit
						3bb58b5719
					
				| @ -23,6 +23,9 @@ mist = ">= 4.0.7 and < 5.0.0" | |||||||
| argv = ">= 1.0.2 and < 2.0.0" | argv = ">= 1.0.2 and < 2.0.0" | ||||||
| gleam_erlang = ">= 0.34.0 and < 1.0.0" | gleam_erlang = ">= 0.34.0 and < 1.0.0" | ||||||
| gleam_http = ">= 4.0.0 and < 5.0.0" | gleam_http = ">= 4.0.0 and < 5.0.0" | ||||||
|  | gleam_otp = ">= 0.16.1 and < 1.0.0" | ||||||
|  | gleam_crypto = ">= 1.5.0 and < 2.0.0" | ||||||
|  | gleam_json = ">= 2.3.0 and < 3.0.0" | ||||||
| 
 | 
 | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| gleeunit = ">= 1.0.0 and < 2.0.0" | gleeunit = ">= 1.0.0 and < 2.0.0" | ||||||
|  | |||||||
| @ -34,8 +34,11 @@ packages = [ | |||||||
| 
 | 
 | ||||||
| [requirements] | [requirements] | ||||||
| argv = { version = ">= 1.0.2 and < 2.0.0" } | argv = { version = ">= 1.0.2 and < 2.0.0" } | ||||||
|  | gleam_crypto = { version = ">= 1.5.0 and < 2.0.0" } | ||||||
| gleam_erlang = { version = ">= 0.34.0 and < 1.0.0" } | gleam_erlang = { version = ">= 0.34.0 and < 1.0.0" } | ||||||
| gleam_http = { version = ">= 4.0.0 and < 5.0.0" } | gleam_http = { version = ">= 4.0.0 and < 5.0.0" } | ||||||
|  | gleam_json = { version = ">= 2.3.0 and < 3.0.0" } | ||||||
|  | gleam_otp = { version = ">= 0.16.1 and < 1.0.0" } | ||||||
| gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } | gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } | ||||||
| gleeunit = { version = ">= 1.0.0 and < 2.0.0" } | gleeunit = { version = ">= 1.0.0 and < 2.0.0" } | ||||||
| glint = { version = ">= 1.2.1 and < 2.0.0" } | glint = { version = ">= 1.2.1 and < 2.0.0" } | ||||||
|  | |||||||
| @ -1,67 +1,47 @@ | |||||||
| import app/web | import app/web | ||||||
|  | import gleam/bit_array | ||||||
| import gleam/bytes_tree | import gleam/bytes_tree | ||||||
|  | import gleam/crypto | ||||||
|  | import gleam/dynamic/decode | ||||||
|  | import gleam/erlang/process | ||||||
|  | import gleam/float | ||||||
| import gleam/http/request.{type Request} | import gleam/http/request.{type Request} | ||||||
| import gleam/http/response.{type Response} | import gleam/http/response.{type Response} | ||||||
| import gleam/option.{None} | import gleam/io | ||||||
|  | import gleam/json | ||||||
|  | import gleam/list | ||||||
|  | import gleam/option.{None, Some} | ||||||
|  | import gleam/otp/actor | ||||||
| import gleam/result | import gleam/result | ||||||
| import gleam/string | import gleam/string | ||||||
| import mist.{type Connection, type ResponseData} | import mist.{type Connection, type ResponseData} | ||||||
| 
 | 
 | ||||||
| pub fn handle_request( | pub fn handle_request( | ||||||
|   request: Request(Connection), |   req: Request(Connection), | ||||||
|   ctx: web.Context, |   ctx: web.Context, | ||||||
| ) -> Response(ResponseData) { | ) -> Response(ResponseData) { | ||||||
|   case request.path_segments(request) { |   io.println("Got request: " <> req.path) | ||||||
|     [] -> serve_frontend() |   case request.path_segments(req) { | ||||||
|     ["static", "frontend.wasm"] -> serve_wasm(ctx) |     [] -> serve_wasm(ctx, "index.html", "text/html") | ||||||
|  |     ["static", "frontend.js"] -> | ||||||
|  |       serve_wasm(ctx, "frontend.js", "text/javascript") | ||||||
|  |     ["static", "frontend_bg.wasm"] -> | ||||||
|  |       serve_wasm(ctx, "frontend_bg.wasm", "application/wasm") | ||||||
|  |     ["ws"] -> serve_websocket(ctx, req) | ||||||
|     _ -> response.set_body(response.new(404), mist.Bytes(bytes_tree.new())) |     _ -> response.set_body(response.new(404), mist.Bytes(bytes_tree.new())) | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const game_body = " | fn serve_wasm( | ||||||
| <html lang=\"en\"> |   ctx: web.Context, | ||||||
| 
 |   file: String, | ||||||
| <head> |   mimetype: String, | ||||||
|     <meta charset=\"utf-8\"> | ) -> Response(ResponseData) { | ||||||
|     <title>TITLE</title> |   let wasm_path = string.join([ctx.config.static_path, file], "/") | ||||||
|     <style> |  | ||||||
|         html, |  | ||||||
|         body, |  | ||||||
|         canvas { |  | ||||||
|             margin: 0px; |  | ||||||
|             padding: 0px; |  | ||||||
|             width: 100%; |  | ||||||
|             height: 100%; |  | ||||||
|             overflow: hidden; |  | ||||||
|             position: absolute; |  | ||||||
|             background: black; |  | ||||||
|             z-index: 0; |  | ||||||
|         } |  | ||||||
|     </style> |  | ||||||
| </head> |  | ||||||
| 
 |  | ||||||
| <body> |  | ||||||
|     <canvas id=\"glcanvas\" tabindex='1'></canvas> |  | ||||||
|     <!-- Minified and statically hosted version of https://github.com/not-fl3/macroquad/blob/master/js/mq_js_bundle.js --> |  | ||||||
|     <script src=\"https://not-fl3.github.io/miniquad-samples/mq_js_bundle.js\"></script> |  | ||||||
|     <script>load(\"static/frontend.wasm\");</script> |  | ||||||
| </body> |  | ||||||
| 
 |  | ||||||
| </html> |  | ||||||
| " |  | ||||||
| 
 |  | ||||||
| fn serve_frontend() -> Response(ResponseData) { |  | ||||||
|   response.new(200) |  | ||||||
|   |> response.set_body(mist.Bytes(bytes_tree.from_string(game_body))) |  | ||||||
|   |> response.set_header("content-type", "text/html") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn serve_wasm(ctx: web.Context) -> Response(ResponseData) { |  | ||||||
|   let wasm_path = string.join([ctx.config.static_path, "frontend.wasm"], "/") |  | ||||||
|   mist.send_file(wasm_path, offset: 0, limit: None) |   mist.send_file(wasm_path, offset: 0, limit: None) | ||||||
|   |> result.map(fn(file) { |   |> result.map(fn(file) { | ||||||
|     response.new(200) |     response.new(200) | ||||||
|     |> response.prepend_header("content-type", "application/wasm") |     |> response.prepend_header("content-type", mimetype) | ||||||
|     |> response.set_body(file) |     |> response.set_body(file) | ||||||
|   }) |   }) | ||||||
|   |> result.lazy_unwrap(fn() { |   |> result.lazy_unwrap(fn() { | ||||||
| @ -69,3 +49,107 @@ fn serve_wasm(ctx: web.Context) -> Response(ResponseData) { | |||||||
|     |> response.set_body(mist.Bytes(bytes_tree.new())) |     |> response.set_body(mist.Bytes(bytes_tree.new())) | ||||||
|   }) |   }) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | fn serve_websocket( | ||||||
|  |   ctx: web.Context, | ||||||
|  |   req: Request(Connection), | ||||||
|  | ) -> Response(ResponseData) { | ||||||
|  |   mist.websocket( | ||||||
|  |     request: req, | ||||||
|  |     on_init: fn(_conn) { #(ctx, Some(ctx.selector)) }, | ||||||
|  |     on_close: fn(_state) { io.println("goodbye!") }, | ||||||
|  |     handler: handle_ws_message, | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn decode_error_to_string(decode_err: decode.DecodeError) -> String { | ||||||
|  |   "Path: " | ||||||
|  |   <> string.join(decode_err.path, ".") | ||||||
|  |   <> " Expected: " | ||||||
|  |   <> decode_err.expected | ||||||
|  |   <> " Got: " | ||||||
|  |   <> decode_err.found | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn decode_errors_to_string(decode_errs: List(decode.DecodeError)) -> String { | ||||||
|  |   string.join(list.map(decode_errs, decode_error_to_string), "\n") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn handle_ws_message(state, conn, message) { | ||||||
|  |   io.println("Got WS message") | ||||||
|  |   case message { | ||||||
|  |     mist.Text("ping") -> { | ||||||
|  |       io.println("Got Ping") | ||||||
|  |       let assert Ok(_) = mist.send_text_frame(conn, "pong") | ||||||
|  |       actor.continue(state) | ||||||
|  |     } | ||||||
|  |     mist.Text(msg) -> { | ||||||
|  |       echo msg | ||||||
|  |       case json.parse(msg, web.client_request_decoder()) { | ||||||
|  |         Error(err) -> { | ||||||
|  |           let err_msg = case err { | ||||||
|  |             json.UnableToDecode(decode_err) -> | ||||||
|  |               "Decode Error: " <> decode_errors_to_string(decode_err) | ||||||
|  |             json.UnexpectedByte(bad_byte) -> "Unexpeted byte: " <> bad_byte | ||||||
|  |             json.UnexpectedEndOfInput -> "Reached EoF" | ||||||
|  |             json.UnexpectedFormat(_) -> "Unexpected format" | ||||||
|  |             json.UnexpectedSequence(seq_msg) -> | ||||||
|  |               "Unexpected Sequence: " <> seq_msg | ||||||
|  |           } | ||||||
|  |           io.println("Could not parse client message: " <> err_msg) | ||||||
|  |         } | ||||||
|  |         Ok(req) -> { | ||||||
|  |           let resp = handle_client_msg(state, req) | ||||||
|  |           echo resp | ||||||
|  |           let resp_str = | ||||||
|  |             web.encode_game_response(resp) | ||||||
|  |             |> json.to_string | ||||||
|  | 
 | ||||||
|  |           let assert Ok(_) = mist.send_text_frame(conn, resp_str) | ||||||
|  |           Nil | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       actor.continue(state) | ||||||
|  |     } | ||||||
|  |     mist.Binary(_) -> { | ||||||
|  |       actor.continue(state) | ||||||
|  |     } | ||||||
|  |     mist.Custom(_) -> { | ||||||
|  |       actor.continue(state) | ||||||
|  |     } | ||||||
|  |     mist.Closed | mist.Shutdown -> actor.Stop(process.Normal) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn handle_client_msg( | ||||||
|  |   _ctx: web.Context, | ||||||
|  |   req: web.ClientRequest, | ||||||
|  | ) -> web.GameResponse { | ||||||
|  |   case req.msg { | ||||||
|  |     web.BuzzIn(time) -> { | ||||||
|  |       io.println("Got buzz in @ " <> float.to_string(time)) | ||||||
|  |       web.AckBuzzer | ||||||
|  |     } | ||||||
|  |     web.Register(username, gamecode) -> { | ||||||
|  |       let secret = crypto.strong_random_bytes(16) | ||||||
|  |       let token = | ||||||
|  |         crypto.new_hasher(crypto.Sha512) | ||||||
|  |         |> crypto.hash_chunk(secret) | ||||||
|  |         |> crypto.hash_chunk(bit_array.from_string(username)) | ||||||
|  |         |> crypto.hash_chunk(bit_array.from_string(gamecode)) | ||||||
|  |         |> crypto.digest | ||||||
|  | 
 | ||||||
|  |       let token_hash = | ||||||
|  |         crypto.new_hasher(crypto.Sha512) | ||||||
|  |         |> crypto.hash_chunk(token) | ||||||
|  |         |> crypto.digest | ||||||
|  | 
 | ||||||
|  |       io.println("New token: " <> bit_array.base64_encode(token_hash, True)) | ||||||
|  | 
 | ||||||
|  |       let token = bit_array.base64_encode(token, True) | ||||||
|  | 
 | ||||||
|  |       web.JoinResponse(token, username) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,7 +1,70 @@ | |||||||
| import config | import config | ||||||
|  | import gleam/dynamic/decode | ||||||
|  | import gleam/erlang/process | ||||||
|  | import gleam/json | ||||||
| import session | import session | ||||||
| import storail | import storail | ||||||
| 
 | 
 | ||||||
| pub type Context { | pub type ClientMessage { | ||||||
|   Context(config: config.Config, sessions: storail.Collection(session.Session)) |   BuzzIn(time: Float) | ||||||
|  |   Register(game_code: String, username: String) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn client_message_decoder() -> decode.Decoder(ClientMessage) { | ||||||
|  |   use variant <- decode.field("type", decode.string) | ||||||
|  |   case variant { | ||||||
|  |     "BuzzIn" -> { | ||||||
|  |       use time <- decode.field("time", decode.float) | ||||||
|  |       decode.success(BuzzIn(time:)) | ||||||
|  |     } | ||||||
|  |     "Register" -> { | ||||||
|  |       use game_code <- decode.field("game_code", decode.string) | ||||||
|  |       use username <- decode.field("username", decode.string) | ||||||
|  |       decode.success(Register(game_code:, username:)) | ||||||
|  |     } | ||||||
|  |     _ -> decode.failure(BuzzIn(time: 0.0), "ClientMessage") | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub type ClientRequest { | ||||||
|  |   ClientRequest(token: String, msg: ClientMessage) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn client_request_decoder() -> decode.Decoder(ClientRequest) { | ||||||
|  |   use token <- decode.field("token", decode.string) | ||||||
|  |   use msg <- decode.field("msg", client_message_decoder()) | ||||||
|  |   decode.success(ClientRequest(token:, msg:)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub type GameResponse { | ||||||
|  |   JoinResponse(username: String, token: String) | ||||||
|  |   AckBuzzer | ||||||
|  |   ResetBuzzer | ||||||
|  |   UpdatePointTotal(score: Int) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn encode_game_response(game_response: GameResponse) -> json.Json { | ||||||
|  |   case game_response { | ||||||
|  |     JoinResponse(..) -> | ||||||
|  |       json.object([ | ||||||
|  |         #("type", json.string("JoinResponse")), | ||||||
|  |         #("username", json.string(game_response.username)), | ||||||
|  |         #("token", json.string(game_response.token)), | ||||||
|  |       ]) | ||||||
|  |     AckBuzzer -> json.object([#("type", json.string("AckBuzzer"))]) | ||||||
|  |     ResetBuzzer -> json.object([#("type", json.string("ResetBuzzer"))]) | ||||||
|  |     UpdatePointTotal(..) -> | ||||||
|  |       json.object([ | ||||||
|  |         #("type", json.string("UpdatePointTotal")), | ||||||
|  |         #("score", json.int(game_response.score)), | ||||||
|  |       ]) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub type Context { | ||||||
|  |   Context( | ||||||
|  |     config: config.Config, | ||||||
|  |     sessions: storail.Collection(session.Session), | ||||||
|  |     selector: process.Selector(ClientRequest), | ||||||
|  |   ) | ||||||
| } | } | ||||||
|  | |||||||
| @ -49,7 +49,11 @@ pub fn run_server() -> glint.Command(Result(Nil, Error)) { | |||||||
|   let storail_cfg = storail.Config(cfg.storage_path) |   let storail_cfg = storail.Config(cfg.storage_path) | ||||||
| 
 | 
 | ||||||
|   let ctx = |   let ctx = | ||||||
|     web.Context(config: cfg, sessions: setup_session_collection(storail_cfg)) |     web.Context( | ||||||
|  |       config: cfg, | ||||||
|  |       sessions: setup_session_collection(storail_cfg), | ||||||
|  |       selector: process.new_selector(), | ||||||
|  |     ) | ||||||
| 
 | 
 | ||||||
|   let handler = router.handle_request(_, ctx) |   let handler = router.handle_request(_, ctx) | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								frontend/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								frontend/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +1,2 @@ | |||||||
| /target | /target | ||||||
|  | /dist | ||||||
|  | |||||||
							
								
								
									
										470
									
								
								frontend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										470
									
								
								frontend/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -26,6 +26,21 @@ version = "1.3.2" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "block-buffer" | ||||||
|  | version = "0.10.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" | ||||||
|  | dependencies = [ | ||||||
|  |  "generic-array", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "bumpalo" | ||||||
|  | version = "3.17.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "bytemuck" | name = "bytemuck" | ||||||
| version = "1.23.0" | version = "1.23.0" | ||||||
| @ -38,6 +53,12 @@ version = "1.5.0" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "bytes" | ||||||
|  | version = "1.10.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "cfg-if" | name = "cfg-if" | ||||||
| version = "1.0.0" | version = "1.0.0" | ||||||
| @ -50,6 +71,15 @@ version = "1.1.0" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "cpufeatures" | ||||||
|  | version = "0.2.17" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" | ||||||
|  | dependencies = [ | ||||||
|  |  "libc", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "crc32fast" | name = "crc32fast" | ||||||
| version = "1.4.2" | version = "1.4.2" | ||||||
| @ -59,12 +89,62 @@ dependencies = [ | |||||||
|  "cfg-if", |  "cfg-if", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "crypto-common" | ||||||
|  | version = "0.1.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" | ||||||
|  | dependencies = [ | ||||||
|  |  "generic-array", | ||||||
|  |  "typenum", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "data-encoding" | ||||||
|  | version = "2.9.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "digest" | ||||||
|  | version = "0.10.7" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" | ||||||
|  | dependencies = [ | ||||||
|  |  "block-buffer", | ||||||
|  |  "crypto-common", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "document-features" | ||||||
|  | version = "0.2.11" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" | ||||||
|  | dependencies = [ | ||||||
|  |  "litrs", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "equivalent" | name = "equivalent" | ||||||
| version = "1.0.2" | version = "1.0.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "ewebsock" | ||||||
|  | version = "0.8.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "679247b4a005c82218a5f13b713239b0b6d484ec25347a719f5b7066152a748a" | ||||||
|  | dependencies = [ | ||||||
|  |  "document-features", | ||||||
|  |  "js-sys", | ||||||
|  |  "log", | ||||||
|  |  "tungstenite", | ||||||
|  |  "wasm-bindgen", | ||||||
|  |  "wasm-bindgen-futures", | ||||||
|  |  "web-sys", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "fdeflate" | name = "fdeflate" | ||||||
| version = "0.3.7" | version = "0.3.7" | ||||||
| @ -84,6 +164,12 @@ dependencies = [ | |||||||
|  "miniz_oxide", |  "miniz_oxide", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "fnv" | ||||||
|  | version = "1.0.7" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "foldhash" | name = "foldhash" | ||||||
| version = "0.1.5" | version = "0.1.5" | ||||||
| @ -104,7 +190,32 @@ dependencies = [ | |||||||
| name = "frontend" | name = "frontend" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  |  "ewebsock", | ||||||
|  "macroquad", |  "macroquad", | ||||||
|  |  "serde", | ||||||
|  |  "serde_json", | ||||||
|  |  "web-time", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "generic-array" | ||||||
|  | version = "0.14.7" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" | ||||||
|  | dependencies = [ | ||||||
|  |  "typenum", | ||||||
|  |  "version_check", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "getrandom" | ||||||
|  | version = "0.2.16" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" | ||||||
|  | dependencies = [ | ||||||
|  |  "cfg-if", | ||||||
|  |  "libc", | ||||||
|  |  "wasi", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -124,6 +235,23 @@ dependencies = [ | |||||||
|  "foldhash", |  "foldhash", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "http" | ||||||
|  | version = "1.3.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" | ||||||
|  | dependencies = [ | ||||||
|  |  "bytes", | ||||||
|  |  "fnv", | ||||||
|  |  "itoa", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "httparse" | ||||||
|  | version = "1.10.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "image" | name = "image" | ||||||
| version = "0.24.9" | version = "0.24.9" | ||||||
| @ -137,12 +265,40 @@ dependencies = [ | |||||||
|  "png", |  "png", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "itoa" | ||||||
|  | version = "1.0.15" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "js-sys" | ||||||
|  | version = "0.3.77" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" | ||||||
|  | dependencies = [ | ||||||
|  |  "once_cell", | ||||||
|  |  "wasm-bindgen", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "libc" | name = "libc" | ||||||
| version = "0.2.172" | version = "0.2.172" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "litrs" | ||||||
|  | version = "0.4.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "log" | ||||||
|  | version = "0.4.27" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "macroquad" | name = "macroquad" | ||||||
| version = "0.4.14" | version = "0.4.14" | ||||||
| @ -172,6 +328,12 @@ dependencies = [ | |||||||
|  "libc", |  "libc", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "memchr" | ||||||
|  | version = "2.7.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "miniquad" | name = "miniquad" | ||||||
| version = "0.4.8" | version = "0.4.8" | ||||||
| @ -218,6 +380,12 @@ dependencies = [ | |||||||
|  "malloc_buf", |  "malloc_buf", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "once_cell" | ||||||
|  | version = "1.21.3" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "png" | name = "png" | ||||||
| version = "0.17.16" | version = "0.17.16" | ||||||
| @ -231,24 +399,306 @@ dependencies = [ | |||||||
|  "miniz_oxide", |  "miniz_oxide", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "ppv-lite86" | ||||||
|  | version = "0.2.21" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" | ||||||
|  | dependencies = [ | ||||||
|  |  "zerocopy", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "proc-macro2" | ||||||
|  | version = "1.0.95" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" | ||||||
|  | dependencies = [ | ||||||
|  |  "unicode-ident", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "quad-rand" | name = "quad-rand" | ||||||
| version = "0.2.3" | version = "0.2.3" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "5a651516ddc9168ebd67b24afd085a718be02f8858fe406591b013d101ce2f40" | checksum = "5a651516ddc9168ebd67b24afd085a718be02f8858fe406591b013d101ce2f40" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "quote" | ||||||
|  | version = "1.0.40" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "rand" | ||||||
|  | version = "0.8.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" | ||||||
|  | dependencies = [ | ||||||
|  |  "libc", | ||||||
|  |  "rand_chacha", | ||||||
|  |  "rand_core", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "rand_chacha" | ||||||
|  | version = "0.3.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" | ||||||
|  | dependencies = [ | ||||||
|  |  "ppv-lite86", | ||||||
|  |  "rand_core", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "rand_core" | ||||||
|  | version = "0.6.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" | ||||||
|  | dependencies = [ | ||||||
|  |  "getrandom", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "rustversion" | ||||||
|  | version = "1.0.20" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "ryu" | ||||||
|  | version = "1.0.20" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "serde" | ||||||
|  | version = "1.0.219" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" | ||||||
|  | dependencies = [ | ||||||
|  |  "serde_derive", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "serde_derive" | ||||||
|  | version = "1.0.219" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "serde_json" | ||||||
|  | version = "1.0.140" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" | ||||||
|  | dependencies = [ | ||||||
|  |  "itoa", | ||||||
|  |  "memchr", | ||||||
|  |  "ryu", | ||||||
|  |  "serde", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "sha1" | ||||||
|  | version = "0.10.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" | ||||||
|  | dependencies = [ | ||||||
|  |  "cfg-if", | ||||||
|  |  "cpufeatures", | ||||||
|  |  "digest", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "simd-adler32" | name = "simd-adler32" | ||||||
| version = "0.3.7" | version = "0.3.7" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "syn" | ||||||
|  | version = "2.0.101" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "unicode-ident", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "thiserror" | ||||||
|  | version = "1.0.69" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" | ||||||
|  | dependencies = [ | ||||||
|  |  "thiserror-impl", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "thiserror-impl" | ||||||
|  | version = "1.0.69" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "ttf-parser" | name = "ttf-parser" | ||||||
| version = "0.21.1" | version = "0.21.1" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" | checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "tungstenite" | ||||||
|  | version = "0.24.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" | ||||||
|  | dependencies = [ | ||||||
|  |  "byteorder", | ||||||
|  |  "bytes", | ||||||
|  |  "data-encoding", | ||||||
|  |  "http", | ||||||
|  |  "httparse", | ||||||
|  |  "log", | ||||||
|  |  "rand", | ||||||
|  |  "sha1", | ||||||
|  |  "thiserror", | ||||||
|  |  "utf-8", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "typenum" | ||||||
|  | version = "1.18.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "unicode-ident" | ||||||
|  | version = "1.0.18" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "utf-8" | ||||||
|  | version = "0.7.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "version_check" | ||||||
|  | version = "0.9.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "wasi" | ||||||
|  | version = "0.11.0+wasi-snapshot-preview1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "wasm-bindgen" | ||||||
|  | version = "0.2.100" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" | ||||||
|  | dependencies = [ | ||||||
|  |  "cfg-if", | ||||||
|  |  "once_cell", | ||||||
|  |  "rustversion", | ||||||
|  |  "wasm-bindgen-macro", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "wasm-bindgen-backend" | ||||||
|  | version = "0.2.100" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" | ||||||
|  | dependencies = [ | ||||||
|  |  "bumpalo", | ||||||
|  |  "log", | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn", | ||||||
|  |  "wasm-bindgen-shared", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "wasm-bindgen-futures" | ||||||
|  | version = "0.4.50" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" | ||||||
|  | dependencies = [ | ||||||
|  |  "cfg-if", | ||||||
|  |  "js-sys", | ||||||
|  |  "once_cell", | ||||||
|  |  "wasm-bindgen", | ||||||
|  |  "web-sys", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "wasm-bindgen-macro" | ||||||
|  | version = "0.2.100" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" | ||||||
|  | dependencies = [ | ||||||
|  |  "quote", | ||||||
|  |  "wasm-bindgen-macro-support", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "wasm-bindgen-macro-support" | ||||||
|  | version = "0.2.100" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn", | ||||||
|  |  "wasm-bindgen-backend", | ||||||
|  |  "wasm-bindgen-shared", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "wasm-bindgen-shared" | ||||||
|  | version = "0.2.100" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" | ||||||
|  | dependencies = [ | ||||||
|  |  "unicode-ident", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "web-sys" | ||||||
|  | version = "0.3.77" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" | ||||||
|  | dependencies = [ | ||||||
|  |  "js-sys", | ||||||
|  |  "wasm-bindgen", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "web-time" | ||||||
|  | version = "1.1.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" | ||||||
|  | dependencies = [ | ||||||
|  |  "js-sys", | ||||||
|  |  "wasm-bindgen", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "winapi" | name = "winapi" | ||||||
| version = "0.3.9" | version = "0.3.9" | ||||||
| @ -270,3 +720,23 @@ name = "winapi-x86_64-pc-windows-gnu" | |||||||
| version = "0.4.0" | version = "0.4.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "zerocopy" | ||||||
|  | version = "0.8.25" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" | ||||||
|  | dependencies = [ | ||||||
|  |  "zerocopy-derive", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "zerocopy-derive" | ||||||
|  | version = "0.8.25" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn", | ||||||
|  | ] | ||||||
|  | |||||||
| @ -4,4 +4,8 @@ version = "0.1.0" | |||||||
| edition = "2024" | edition = "2024" | ||||||
| 
 | 
 | ||||||
| [dependencies] | [dependencies] | ||||||
|  | ewebsock = "0.8.0" | ||||||
| macroquad = "0.4.14" | macroquad = "0.4.14" | ||||||
|  | serde = { version = "1.0.219", features = ["derive"] } | ||||||
|  | serde_json = "1.0.140" | ||||||
|  | web-time = "1.1.0" | ||||||
|  | |||||||
							
								
								
									
										132
									
								
								frontend/build_wasm.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										132
									
								
								frontend/build_wasm.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,132 @@ | |||||||
|  | #!/usr/bin/env bash | ||||||
|  | 
 | ||||||
|  | set -e | ||||||
|  | 
 | ||||||
|  | HELP_STRING=$( | ||||||
|  | 	cat <<-END | ||||||
|  | 		usage: build_wasm.sh PROJECT_NAME [--release] | ||||||
|  | 		Build script for combining a Macroquad project with wasm-bindgen, | ||||||
|  | 		allowing integration with the greater wasm-ecosystem. | ||||||
|  | 		example: ./build_wasm.sh flappy-bird | ||||||
|  | 		  This'll go through the following steps: | ||||||
|  | 			    1. Build as target 'wasm32-unknown-unknown'. | ||||||
|  | 			    2. Create the directory 'dist' if it doesn't already exist. | ||||||
|  | 			    3. Run wasm-bindgen with output into the 'dist' directory. | ||||||
|  | 		            - If the '--release' flag is provided, the build will be optimized for release. | ||||||
|  | 			    4. Apply patches to the output js file (detailed here: https://github.com/not-fl3/macroquad/issues/212#issuecomment-835276147). | ||||||
|  | 			    5. Generate coresponding 'index.html' file. | ||||||
|  | 			Author: Tom Solberg <me@sbg.dev> | ||||||
|  | 			Edit: Nik codes <nik.code.things@gmail.com> | ||||||
|  | 			Edit: Nobbele <realnobbele@gmail.com> | ||||||
|  | 			Edit: profan <robinhubner@gmail.com> | ||||||
|  | 			Edit: Nik codes <nik.code.things@gmail.com> | ||||||
|  | 			Version: 0.4 | ||||||
|  | 	END | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | die() { | ||||||
|  | 	echo >&2 "$HELP_STRING" | ||||||
|  | 	echo >&2 | ||||||
|  | 	echo >&2 "Error: $*" | ||||||
|  | 	exit 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # Parse primary commands | ||||||
|  | while [[ $# -gt 0 ]]; do | ||||||
|  | 	key="$1" | ||||||
|  | 	case $key in | ||||||
|  | 	--release) | ||||||
|  | 		RELEASE=yes | ||||||
|  | 		shift | ||||||
|  | 		;; | ||||||
|  | 
 | ||||||
|  | 	-h | --help) | ||||||
|  | 		echo "$HELP_STRING" | ||||||
|  | 		exit 0 | ||||||
|  | 		;; | ||||||
|  | 
 | ||||||
|  | 	*) | ||||||
|  | 		POSITIONAL+=("$1") | ||||||
|  | 		shift | ||||||
|  | 		;; | ||||||
|  | 	esac | ||||||
|  | done | ||||||
|  | 
 | ||||||
|  | # Restore positionals | ||||||
|  | set -- "${POSITIONAL[@]}" | ||||||
|  | [ $# -ne 1 ] && die "too many arguments provided" | ||||||
|  | 
 | ||||||
|  | PROJECT_NAME=$1 | ||||||
|  | 
 | ||||||
|  | HTML=$( | ||||||
|  | 	cat <<-END | ||||||
|  | 		<html lang="en"> | ||||||
|  | 		<head> | ||||||
|  | 		    <meta charset="utf-8"> | ||||||
|  | 		    <title>${PROJECT_NAME}</title> | ||||||
|  | 		    <style> | ||||||
|  | 		        html, | ||||||
|  | 		        body, | ||||||
|  | 		        canvas { | ||||||
|  | 		            margin: 0px; | ||||||
|  | 		            padding: 0px; | ||||||
|  | 		            width: 100%; | ||||||
|  | 		            height: 100%; | ||||||
|  | 		            overflow: hidden; | ||||||
|  | 		            position: absolute; | ||||||
|  | 		            z-index: 0; | ||||||
|  | 		        } | ||||||
|  | 		    </style> | ||||||
|  | 		</head> | ||||||
|  | 		<body style="margin: 0; padding: 0; height: 100vh; width: 100vw;"> | ||||||
|  | 		    <canvas id="glcanvas" tabindex='1' hidden></canvas> | ||||||
|  | 		    <script src="https://not-fl3.github.io/miniquad-samples/mq_js_bundle.js"></script> | ||||||
|  | 		    <script type="module"> | ||||||
|  | 		        import init, { set_wasm } from "./static/${PROJECT_NAME}.js"; | ||||||
|  | 		        async function impl_run() { | ||||||
|  | 		            let wbg = await init(); | ||||||
|  | 		            miniquad_add_plugin({ | ||||||
|  | 		                register_plugin: (a) => (a.wbg = wbg), | ||||||
|  | 		                on_init: () => set_wasm(wasm_exports), | ||||||
|  | 		                version: "0.0.1", | ||||||
|  | 		                name: "wbg", | ||||||
|  | 		            }); | ||||||
|  | 		            load("./static/${PROJECT_NAME}_bg.wasm"); | ||||||
|  | 		        } | ||||||
|  | 		        window.run = function() { | ||||||
|  | 		            document.getElementById("run-container").remove(); | ||||||
|  | 		            document.getElementById("glcanvas").removeAttribute("hidden"); | ||||||
|  | 		            document.getElementById("glcanvas").focus(); | ||||||
|  | 		            impl_run(); | ||||||
|  | 		        } | ||||||
|  | 		    </script> | ||||||
|  | 		    <div id="run-container" style="display: flex; justify-content: center; align-items: center; height: 100%; flex-direction: column;"> | ||||||
|  | 		        <button onclick="run()">Run Game</button> | ||||||
|  | 		    </div> | ||||||
|  | 		</body> | ||||||
|  | 		</html> | ||||||
|  | 	END | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | TARGET_DIR="target/wasm32-unknown-unknown" | ||||||
|  | # Build | ||||||
|  | if [ -n "$RELEASE" ]; then | ||||||
|  | 	cargo build --release --target wasm32-unknown-unknown | ||||||
|  | 	TARGET_DIR="$TARGET_DIR/release" | ||||||
|  | else | ||||||
|  | 	cargo build --target wasm32-unknown-unknown | ||||||
|  | 	TARGET_DIR="$TARGET_DIR/debug" | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | # Generate bindgen outputs | ||||||
|  | mkdir -p dist | ||||||
|  | wasm-bindgen $TARGET_DIR/"$PROJECT_NAME".wasm --out-dir dist --target web --no-typescript | ||||||
|  | 
 | ||||||
|  | # Shim to tie the thing together | ||||||
|  | sed -i "s/import \* as __wbg_star0 from 'env';//" dist/"$PROJECT_NAME".js | ||||||
|  | sed -i "s/let wasm;/let wasm; export const set_wasm = (w) => wasm = w;/" dist/"$PROJECT_NAME".js | ||||||
|  | sed -i "s/imports\['env'\] = __wbg_star0;/return imports.wbg\;/" dist/"$PROJECT_NAME".js | ||||||
|  | sed -i "s/const imports = __wbg_get_imports();/return __wbg_get_imports();/" dist/"$PROJECT_NAME".js | ||||||
|  | 
 | ||||||
|  | # Create index from the HTML variable | ||||||
|  | echo "$HTML" >dist/index.html | ||||||
							
								
								
									
										66
									
								
								frontend/src/context.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								frontend/src/context.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | |||||||
|  | use ewebsock::WsEvent; | ||||||
|  | use macroquad::prelude::{error, info}; | ||||||
|  | 
 | ||||||
|  | use crate::model::{ClientMessage, ClientRequest, GameResponse}; | ||||||
|  | 
 | ||||||
|  | pub struct Context { | ||||||
|  |     pub sender: ewebsock::WsSender, | ||||||
|  |     pub reciever: ewebsock::WsReceiver, | ||||||
|  |     pub token: String, | ||||||
|  |     pub username: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Context { | ||||||
|  |     pub fn new(url: &str) -> Context { | ||||||
|  |         let (sender, reciever) = ewebsock::connect(url, ewebsock::Options::default()).unwrap(); | ||||||
|  | 
 | ||||||
|  |         Self { | ||||||
|  |             sender, | ||||||
|  |             reciever, | ||||||
|  |             token: String::new(), | ||||||
|  |             username: String::new(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn send_msg(&mut self, msg: &ClientMessage) { | ||||||
|  |         let request = ClientRequest { | ||||||
|  |             token: self.token.clone(), | ||||||
|  |             msg: msg.clone(), | ||||||
|  |         }; | ||||||
|  |         let msg = serde_json::to_string(&request).unwrap(); | ||||||
|  |         self.sender.send(ewebsock::WsMessage::Text(msg)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn recv_msg(&mut self) -> Option<GameResponse> { | ||||||
|  |         if let Some(msg) = self.reciever.try_recv() { | ||||||
|  |             match msg { | ||||||
|  |                 WsEvent::Opened => { | ||||||
|  |                     info!("Web socket opened!"); | ||||||
|  |                     None | ||||||
|  |                 } | ||||||
|  |                 WsEvent::Message(ws_message) => match ws_message { | ||||||
|  |                     ewebsock::WsMessage::Text(msg) => { | ||||||
|  |                         let game_resp: GameResponse = serde_json::from_str(&msg).unwrap(); | ||||||
|  | 
 | ||||||
|  |                         if let GameResponse::JoinResponse { username, token } = &game_resp { | ||||||
|  |                             self.username = username.clone(); | ||||||
|  |                             self.token = token.clone(); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         Some(game_resp) | ||||||
|  |                     } | ||||||
|  |                     _ => None, | ||||||
|  |                 }, | ||||||
|  |                 WsEvent::Error(err) => { | ||||||
|  |                     error!("Something hecky going on: {}", err); | ||||||
|  |                     None | ||||||
|  |                 } | ||||||
|  |                 WsEvent::Closed => { | ||||||
|  |                     panic!("Socket closed, something up..."); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             None | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										87
									
								
								frontend/src/login_screen.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								frontend/src/login_screen.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,87 @@ | |||||||
|  | use crate::StateTransition; | ||||||
|  | use crate::context::Context; | ||||||
|  | use crate::model::register::Register; | ||||||
|  | use crate::model::{ClientMessage, GameResponse}; | ||||||
|  | use crate::screen::Screen; | ||||||
|  | use macroquad::hash; | ||||||
|  | use macroquad::{prelude::*, ui::root_ui}; | ||||||
|  | 
 | ||||||
|  | #[derive(Default)] | ||||||
|  | pub struct LoginScreen { | ||||||
|  |     new_username: String, | ||||||
|  |     new_room_code: String, | ||||||
|  |     sent_join: bool, | ||||||
|  |     retry_count: usize, | ||||||
|  |     error_msg: bool, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Screen for LoginScreen { | ||||||
|  |     fn handle_frame(&mut self, ctx: &mut Context) -> Option<StateTransition> { | ||||||
|  |         clear_background(WHITE); | ||||||
|  | 
 | ||||||
|  |         let group_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.05); | ||||||
|  |         let window_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.25); | ||||||
|  | 
 | ||||||
|  |         let mut pressed_join = false; | ||||||
|  |         root_ui().window( | ||||||
|  |             hash!("Window"), | ||||||
|  |             Vec2::new( | ||||||
|  |                 screen_width() * 0.5 - window_size.x * 0.5, | ||||||
|  |                 screen_height() * 0.5 - window_size.x * 0.5, | ||||||
|  |             ), | ||||||
|  |             Vec2::new(screen_width() * 0.25, screen_height() * 0.25), | ||||||
|  |             |ui| { | ||||||
|  |                 ui.group(hash!("User Group"), group_size, |ui| { | ||||||
|  |                     ui.input_text(hash!("Username In"), "Username", &mut self.new_username); | ||||||
|  |                 }); | ||||||
|  |                 ui.group(hash!("Room Group"), group_size, |ui| { | ||||||
|  |                     ui.input_text(hash!("Room Code In"), "Room Code", &mut self.new_room_code); | ||||||
|  |                 }); | ||||||
|  |                 ui.group(hash!("Join Group"), group_size, |ui| { | ||||||
|  |                     if ui.button(Vec2::new(group_size.x * 0.45, 0.0), "Join") { | ||||||
|  |                         info!( | ||||||
|  |                             "User pressed joined with name={} room_code={}", | ||||||
|  |                             self.new_username, self.new_room_code | ||||||
|  |                         ); | ||||||
|  | 
 | ||||||
|  |                         pressed_join = true; | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |                 if self.error_msg { | ||||||
|  |                     ui.group(hash!("Error Group"), group_size, |ui| { | ||||||
|  |                         ui.label(Vec2::new(0.0, 0.0), "Failed to join game, try again..."); | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         if pressed_join { | ||||||
|  |             ctx.send_msg(&ClientMessage::Register(Register { | ||||||
|  |                 game_code: self.new_room_code.clone(), | ||||||
|  |                 username: self.new_username.clone(), | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |             self.sent_join = true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if self.sent_join { | ||||||
|  |             let msg = ctx.recv_msg(); | ||||||
|  | 
 | ||||||
|  |             if let Some(GameResponse::JoinResponse { username, token }) = msg { | ||||||
|  |                 return Some(StateTransition::JoinAsPlayer { username, token }); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             self.retry_count += 1; | ||||||
|  | 
 | ||||||
|  |             if self.retry_count > get_fps() as usize * 5 { | ||||||
|  |                 warn!("Join failed, try again..."); | ||||||
|  |                 self.retry_count = 0; | ||||||
|  |                 self.sent_join = false; | ||||||
|  |                 self.error_msg = true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         None | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,41 +1,59 @@ | |||||||
| use macroquad::hash; | use context::Context; | ||||||
| use macroquad::{prelude::*, ui::root_ui}; | use login_screen::LoginScreen; | ||||||
|  | use macroquad::prelude::*; | ||||||
|  | use player_screen::PlayerScreen; | ||||||
|  | use screen::Screen; | ||||||
|  | 
 | ||||||
|  | mod context; | ||||||
|  | mod login_screen; | ||||||
|  | mod model; | ||||||
|  | mod player_screen; | ||||||
|  | mod screen; | ||||||
|  | 
 | ||||||
|  | pub enum State { | ||||||
|  |     Init, | ||||||
|  |     Login(LoginScreen), | ||||||
|  |     PlayerScreen(PlayerScreen), | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl State { | ||||||
|  |     pub fn transition_state(&self, transition: StateTransition) -> Self { | ||||||
|  |         match transition { | ||||||
|  |             StateTransition::Init | StateTransition::LeaveGame => { | ||||||
|  |                 Self::Login(LoginScreen::default()) | ||||||
|  |             } | ||||||
|  |             StateTransition::JoinAsPlayer { username, token } => { | ||||||
|  |                 Self::PlayerScreen(PlayerScreen::new(&username, &token)) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn handle_frame(&mut self, ctx: &mut Context) -> Option<StateTransition> { | ||||||
|  |         match self { | ||||||
|  |             Self::Init => Some(StateTransition::Init), | ||||||
|  |             Self::Login(login_screen) => login_screen.handle_frame(ctx), | ||||||
|  |             Self::PlayerScreen(player_screen) => player_screen.handle_frame(ctx), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub enum StateTransition { | ||||||
|  |     Init, | ||||||
|  |     JoinAsPlayer { username: String, token: String }, | ||||||
|  |     LeaveGame, | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| #[macroquad::main("Play of the Game")] | #[macroquad::main("Play of the Game")] | ||||||
| async fn main() { | async fn main() { | ||||||
|     let mut username = String::new(); |     let mut context = Context::new("ws://127.0.0.1:8080/ws"); | ||||||
|     let mut rooom_code = String::new(); | 
 | ||||||
|  |     let mut state = State::Init; | ||||||
|  |     state.transition_state(StateTransition::Init); | ||||||
|  | 
 | ||||||
|     loop { |     loop { | ||||||
|         clear_background(WHITE); |         if let Some(transition) = state.handle_frame(&mut context) { | ||||||
| 
 |             state = state.transition_state(transition); | ||||||
|         let group_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.05); |         } | ||||||
|         let window_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.25); |  | ||||||
| 
 |  | ||||||
|         root_ui().window( |  | ||||||
|             hash!("Window"), |  | ||||||
|             Vec2::new( |  | ||||||
|                 screen_width() * 0.5 - window_size.x * 0.5, |  | ||||||
|                 screen_height() * 0.5 - window_size.x * 0.5, |  | ||||||
|             ), |  | ||||||
|             Vec2::new(screen_width() * 0.25, screen_height() * 0.25), |  | ||||||
|             |ui| { |  | ||||||
|                 ui.group(hash!("User Group"), group_size, |ui| { |  | ||||||
|                     ui.input_text(hash!("Username In"), "Username", &mut username); |  | ||||||
|                 }); |  | ||||||
|                 ui.group(hash!("Room Group"), group_size, |ui| { |  | ||||||
|                     ui.input_text(hash!("Room Code In"), "Room Code", &mut rooom_code); |  | ||||||
|                 }); |  | ||||||
|                 ui.group(hash!("Join Group"), group_size, |ui| { |  | ||||||
|                     if ui.button(Vec2::new(group_size.x * 0.45, 0.0), "Join") { |  | ||||||
|                         info!( |  | ||||||
|                             "User pressed joined with name={} room_code={}", |  | ||||||
|                             username, rooom_code |  | ||||||
|                         ); |  | ||||||
|                     } |  | ||||||
|                 }); |  | ||||||
|             }, |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         next_frame().await |         next_frame().await | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								frontend/src/model/buzz_in.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								frontend/src/model/buzz_in.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | use serde::{Deserialize, Serialize}; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Serialize, Deserialize, Clone)] | ||||||
|  | pub struct BuzzIn { | ||||||
|  |     pub time: f64, | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								frontend/src/model/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								frontend/src/model/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | |||||||
|  | use buzz_in::BuzzIn; | ||||||
|  | use register::Register; | ||||||
|  | use serde::{Deserialize, Serialize}; | ||||||
|  | 
 | ||||||
|  | pub mod buzz_in; | ||||||
|  | pub mod register; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Serialize, Clone)] | ||||||
|  | #[serde(tag = "type")] | ||||||
|  | pub enum ClientMessage { | ||||||
|  |     BuzzIn(BuzzIn), | ||||||
|  |     Register(Register), | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Serialize)] | ||||||
|  | pub struct ClientRequest { | ||||||
|  |     pub token: String, | ||||||
|  |     pub msg: ClientMessage, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Deserialize)] | ||||||
|  | #[serde(tag = "type")] | ||||||
|  | pub enum GameResponse { | ||||||
|  |     JoinResponse { username: String, token: String }, | ||||||
|  |     AckBuzzer, | ||||||
|  |     ResetBuzzer, | ||||||
|  |     UpdatePountTotal { score: i32 }, | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								frontend/src/model/register.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								frontend/src/model/register.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | use serde::Serialize; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Serialize, Clone)] | ||||||
|  | pub struct Register { | ||||||
|  |     pub game_code: String, | ||||||
|  |     pub username: String, | ||||||
|  | } | ||||||
							
								
								
									
										72
									
								
								frontend/src/player_screen.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								frontend/src/player_screen.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | |||||||
|  | use web_time::{SystemTime, UNIX_EPOCH}; | ||||||
|  | 
 | ||||||
|  | use crate::StateTransition; | ||||||
|  | use crate::context::Context; | ||||||
|  | use crate::model::ClientMessage; | ||||||
|  | use crate::model::buzz_in::BuzzIn; | ||||||
|  | use crate::screen::Screen; | ||||||
|  | use macroquad::prelude::*; | ||||||
|  | 
 | ||||||
|  | pub struct PlayerScreen { | ||||||
|  |     token: String, | ||||||
|  |     username: String, | ||||||
|  |     score: i32, | ||||||
|  |     button_pressed: bool, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl PlayerScreen { | ||||||
|  |     pub fn new(token: &str, username: &str) -> Self { | ||||||
|  |         Self { | ||||||
|  |             token: token.to_string(), | ||||||
|  |             username: username.to_string(), | ||||||
|  |             score: 0, | ||||||
|  |             button_pressed: false, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Screen for PlayerScreen { | ||||||
|  |     fn handle_frame(&mut self, ctx: &mut Context) -> Option<StateTransition> { | ||||||
|  |         clear_background(BEIGE); | ||||||
|  | 
 | ||||||
|  |         let button_color = if self.button_pressed { | ||||||
|  |             Color::from_hex(0xaa0000) | ||||||
|  |         } else { | ||||||
|  |             RED | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         let button_size = (screen_width() * 0.33).min(screen_height() * 0.20); | ||||||
|  |         draw_circle( | ||||||
|  |             screen_width() * 0.5, | ||||||
|  |             screen_height() * 0.5, | ||||||
|  |             button_size * 1.1, | ||||||
|  |             DARKBROWN, | ||||||
|  |         ); | ||||||
|  |         draw_circle( | ||||||
|  |             screen_width() * 0.5, | ||||||
|  |             screen_height() * 0.5, | ||||||
|  |             button_size, | ||||||
|  |             button_color, | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         if is_mouse_button_pressed(MouseButton::Left) { | ||||||
|  |             let loc = Vec2::from(mouse_position()); | ||||||
|  |             let normalize = loc - Vec2::new(screen_width() * 0.5, screen_height() * 0.5); | ||||||
|  | 
 | ||||||
|  |             if normalize.length() < button_size { | ||||||
|  |                 let timestamp = SystemTime::now(); | ||||||
|  |                 let unix_timestamp = timestamp.duration_since(UNIX_EPOCH).expect("Time invalid"); | ||||||
|  | 
 | ||||||
|  |                 let time = unix_timestamp.as_secs_f64(); | ||||||
|  | 
 | ||||||
|  |                 info!("Button pressed @ {}", time); | ||||||
|  |                 self.button_pressed = true; | ||||||
|  |                 let buzz_in = BuzzIn { time }; | ||||||
|  | 
 | ||||||
|  |                 ctx.send_msg(&ClientMessage::BuzzIn(buzz_in)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         None | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								frontend/src/screen.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								frontend/src/screen.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | use crate::{StateTransition, context::Context}; | ||||||
|  | 
 | ||||||
|  | pub trait Screen { | ||||||
|  |     fn init_frame(&mut self) {} | ||||||
|  |     fn handle_frame(&mut self, ctx: &mut Context) -> Option<StateTransition>; | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user