Giter Club home page Giter Club logo

ezbrowsergameserver's Introduction

ezbrowsergameserver

Ever wanted to make and self-host one of those simple, usually text-based, multiplayer browser games?

Me too! And this is a small library to hopefully make that as ez as possible :)

Getting started

Before you do anything, know that ezbrowsergameserver isn't a server for your website - it only provides the WebSocket which will be used to keep all your players updated and in sync.

If you have python3 installed, you can use the script examples/server.sh as a quick no-setup web server running on 0.0.0.0:8080.

For this example, you will use 0.0.0.0:8080/00_min.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Min - ezbrowsergameserver</title>
    <meta name="color-scheme" content="light dark">
    <script>
      var con = undefined;
      function createLobby() {
        joinLobby("new");
      }
      function joinLobby(id) {
        let ip;
        // if possible, use the same host that is hosting this html file.
        // assume localhost if not possible (i.e. opened the file directly)
        if (window.location.hostname) {
          ip = window.location.hostname;
        } else {
          ip = "0.0.0.0";
        }
        // connect to the websocket
        con = new WebSocket("ws://" + ip + ":8081");
        // handle incoming messages
        con.onmessage = (e) => {
          let msg = e.data;
          // allow the server to update the clients html
          bodyDiv.innerHTML = msg;
        };
        // when the websocket finishes connecting...
        con.onopen = () => {
          // join a lobby
          con.send(id);
        };
      }
    </script>
  </head>
  <body>
    <div id="bodyDiv">
      <p>Lobby ID: <input id="lobbyId"></p>
      <button onclick=createLobby()>Create new lobby</button>
      <button onclick=joinLobbyPressed()>Join lobby by ID</button>
      <script>
        function joinLobbyPressed() {
          joinLobby(lobbyId.value);
        }
      </script>
    </div>
  </body>
</html>

This will let you create a new lobby or join an existing one by entering its ID in the text input. The <script> sections use a WebSocket to connect to your game server - the server you are about to create using this library.

But before we get to your very first ezbrowsergame, you need a lobby.

The lobby is the place where people gather before a game starts. While in the lobby, people can change the gamemode, the game's settings, or anything else you implement for your lobby. The lobby is also the place for your global state, since it still exists when a game ends. Lobbies can be accessed via their ID, formatted as a hex string ({id:X}).

This example will simply start the "game" every time a player joins/leaves the lobby. The "game" will last one second and show the new number of players in the lobby.

These are the imports you will need for the example:

use std::time::Instant;
use ezbrowsergameserver::prelude::*;

First, create a struct for your global state:

struct GlobalState {
    player_count_changed: bool,
}

Then, one for your game:

struct PlayerCountGame(Option<Instant>);

Now, implement LobbyState for your GlobalState struct:

#[async_trait]
impl LobbyState for GlobalState {
    type PlayerState = ();
    fn new() -> Self {
        Self {
            player_count_changed: false,
        }
    }
    fn new_player() -> Self::PlayerState {
        ()
    }
    async fn player_joined(_id: usize, lobby: &mut Lobby<Self>, _player: PlayerIndex) {
        lobby.state.player_count_changed = true;
    }
    async fn player_leaving(_id: usize, lobby: &mut Lobby<Self>, _player: PlayerIndex) {
        lobby.state.player_count_changed = true;
    }
    async fn lobby_update(id: usize, lobby: &mut Lobby<Self>) -> Option<Box<dyn GameState<Self>>> {
        if lobby.reset {
            lobby.reset = false;
            // show lobby screen to all clients
            for (i, player) in lobby.players_mut().enumerate() {
                player
                    .send(format!(
                        "<h1>You are player #{}</h1><p>Lobby ID: {id:X}</p>",
                        i + 1
                    ))
                    .await;
            }
        }
        for player in lobby.players_mut() {
            if let Some(_) = player.get_msg().await {
                // if we don't try to get_msg, we don't detect player disconnects
            }
        }
        if lobby.state.player_count_changed {
            lobby.state.player_count_changed = false;
            // start the "game"
            return Some(Box::new(PlayerCountGame(None)));
        }
        None
    }
}

Then, implement GameState for your PlayerCountGame:

#[async_trait]
impl GameState<GlobalState> for PlayerCountGame {
    async fn update(&mut self, lobby: &mut Lobby<GlobalState>) -> bool {
        // game starts, update all clients
        if self.0.is_none() {
            self.0 = Some(Instant::now());
            let c = lobby.players().len();
            for player in lobby.players_mut() {
                player.send(format!("<h1>There are {c} players</h1>")).await;
            }
        }
        // game ends after 1 seconds
        self.0.is_some_and(|start| start.elapsed().as_secs() >= 1)
    }
    async fn player_leaving(&mut self, lobby: &mut Lobby<GlobalState>, _player: PlayerIndex) {
        lobby.state.player_count_changed = true;
    }
}

And finally, add your main function:

#[tokio::main]
async fn main() {
    host::<GlobalState>("0.0.0.0:8081").await;
}

That's it - we've created a "game".

If you want to try it out, go to the examples/ directory, run ./server.sh, run cargo run --example 00_min, then open 0.0.0.0:8080/00_min.html in a browser and create a new lobby.

QuickStart

cargo new my_project
cd my_project/
cargo add ezbrowsergameserver
cargo add tokio --features macros
echo 'use ezbrowsergameserver::prelude::*;

#[tokio::main]
async fn main() {
    host::<ToDo>("0.0.0.0:8081").await;
}' > src/main.rs

Feel free to experiment with the example files by copying them to src/main.rs and changing different things :)

ezbrowsergameserver's People

Contributors

dummi26 avatar

Stargazers

Matthias Kaak avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.