All Projects → seagreen → hermetic

seagreen / hermetic

Licence: other
Strategy game in Haskell (PRs: ❌)

Programming Languages

haskell
3896 projects
shell
77523 projects

Hermetic

A two player, simultaneous turn desktop strategy game.

screenshot

Install

See docs/install.md.

Player guide

Start here.

Haddocks

Hosted here.

Design

Core module layout:

modules

Components

Game rules

sloc-game

Located at ./src/Game. Completely UI-agnostic, could be broken into its own separate package if we wanted. Imports nothing local outside of Game.*.

Gloss UI

sloc-ui

Everything in ./src outside of ./src/Game/*.

Tracks local state like what base the user has selected. Uses the server to exchange orders with the opponent. When both players have moved uses the game rules to step the game forward.

Server

sloc-json-relay

A local package located at ./json-relay. Provides an executable server which allows clients to join rooms and relays JSON messages between clients in the same room. Knows nothing about this specific game.

MVU

The UI uses a Model/View/Update architecture. The game rules also have a Model and Update, but no View.

This can be summarized with a few type signatures.

Game rules:

-- in Game.Model
data Model = Model
  { modelPlaces :: HashMap PlaceId Place
  ...
  }

-- in Game.Update
update :: HashMap Player Orders -> Model -> Model

Gloss UI:

-- in Model, with Game.Model imported as Game
data Model = Model
  { modelGame      :: Game.Model
  , modelSelection :: Selection
  ...
  }

-- in View
view :: Model -> Picture

-- in Update
update :: Input -> Model -> Model

In Gloss unlike Elm there's no Msg type. That leaves it up to us to figure out how to get the View and Update agreeing on where clickable things are displayed without drowning in duplicate code.

Our solution is the Layout module, which provides a description of where each clickable item is in the UI:

newtype Layout item
  = Layout { unLayout :: [Set item] }

uiLayout :: Model -> Layout Item

This is used by the View to render the UI and by Update to process clicks.

NIH

Having many features or graphics isn't a goal of the game. So we can use simple tools and implement the rest of what we need ourselves.

We use Gloss which provides a keyboard/mouse input type, an image output type, and a MVU app runner. Since we implement everything else the code provides examples of panning, zooming, and mapping mouse clicks to UI items.

Multiplayer

Multiplayer is synchronous.

Each player has a Game.Model. When both players have ended their turns, each calls Game.Update.update with the same inputs (both their and their opponent's orders). This rolls the game model forward to the start of the next turn.

This means Game.Update.update must be deterministic. We avoid functions like Data.HashMap.Strict.toList in the game code.

Each player also has a UI model: Model. These will have different values for each player.

There's no attempt to make the game resistant to bad actors. If someone wants to cheat they can modify their client to view board info that should be hidden.

Why no PRs?

For this particular project I wanted the satisfaction of doing the coding myself.

Fork and add your own twist!

Special thanks

  • Mitchell Rosen: for getting multiplayer working in The Depths, an earlier game this multiplayer implementation is based off of.

  • Gib Jeffries: for map development in Onshape and playtesting.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].