Initial commit of the server

main
Mari 3 years ago
parent 443a47b387
commit abaeb8d609
  1. 19
      server/actions/client/client.go
  2. 1
      server/actions/server/server.go
  3. 18
      server/actions/syncable/action.go
  4. 5
      server/go.mod
  5. 2
      server/go.sum
  6. 1
      server/persistence/persistence.go
  7. 9
      server/state/coordinates.go
  8. 104
      server/state/hexcolor.go
  9. 83
      server/state/hexmap.go
  10. 51
      server/state/hexorientation.go
  11. 49
      server/state/lineparity.go
  12. 15
      server/state/synced.go
  13. 14
      server/state/user.go

@ -0,0 +1,19 @@
package client
import "hexmap-server/actions/syncable"
type Hello struct {
Version int `json:"version"`
}
type Refresh struct {
}
type SentAction struct {
Id int `json:"id"`
Action syncable.Action `json:"action"`
}
type SentActions struct {
Nested []SentAction `json:"nested"`
}

@ -0,0 +1,18 @@
package syncable
import (
"errors"
"hexmap-server/state"
)
var ErrorNoOp error = errors.New("action's effects were already applied, or it's an empty action")
// Action is the interface for actions that can be shared.
type Action interface {
// Apply causes the action's effects to be applied to s, mutating it in place.
// All syncable.Actions must conform to the standard that if an action can't be correctly applied, or if it would
// have no effect, it returns an error without changing s.
// If an action can be correctly applied but would have no effect, it should return ErrorNoOp.
// If an action is correctly applied and has an effect, it should return nil.
Apply(s *state.Synced) error
}

@ -0,0 +1,5 @@
module hexmap-server
go 1.16
require github.com/rs/xid v1.3.0 // indirect

@ -0,0 +1,2 @@
github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=

@ -0,0 +1 @@
package persistence

@ -0,0 +1,9 @@
package state
// StorageCoordinates gives the coordinates of a cell in a form optimized for storage.
type StorageCoordinates struct {
// Line is the index from 0 to Lines - 1 of the HexLine in the HexLayer.
Line uint8 `json:"line"`
// Cell is the index from 0 to CellsPerLine - 1 of the HexCell in the HexLine.
Cell uint8 `json:"cell"`
}

@ -0,0 +1,104 @@
package state
import "fmt"
// HexColor is an internal representation of a hexadecimal color string.
// It can take one of these formats:
// #RRGGBBAA
// #RRGGBB
// #RGBA
// #RGB
// When marshaling, it will always choose the most efficient format, but any format can be used when unmarshaling.
type HexColor struct {
// R is the red component of the color.
R uint8
// G is the green component of the color.
G uint8
// B is the blue component of the color.
B uint8
// A is the alpha component of the color.
A uint8
}
// HexColorFromString decodes a hexadecimal string into a hex color.
func HexColorFromString(text string) (HexColor, error) {
var hex HexColor
if err := (&hex).UnmarshalText([]byte(text)); err != nil {
return hex, err
}
return hex, nil
}
func (h *HexColor) UnmarshalText(text []byte) error {
var count, expected int
var short bool
var scanErr error
switch len(text) {
case 9:
// Long form with alpha: #RRGGBBAA
expected = 4
short = false
count, scanErr = fmt.Sscanf(string(text), "#%02X%02X%02X%02X", &h.R, &h.G, &h.B, &h.A)
case 7:
// Long form: #RRGGBB
expected = 3
h.A = 0xFF
count, scanErr = fmt.Sscanf(string(text), "#%02X%02X%02X", &h.R, &h.G, &h.B)
case 5:
// Short form with alpha: #RGBA
expected = 4
short = true
count, scanErr = fmt.Sscanf(string(text), "#%01X%01X%01X%01X", &h.R, &h.G, &h.B, &h.A)
case 4:
// Short form: #RGB
expected = 3
h.A = 0xF
short = true
count, scanErr = fmt.Sscanf(string(text), "#%01X%01X%01X", &h.R, &h.G, &h.B)
default:
return fmt.Errorf("can't decode %s as HexColor: wrong length", text)
}
if scanErr != nil {
return scanErr
}
if count != expected {
return fmt.Errorf("can't decode %s as HexColor: missing components", text)
}
if short {
h.R *= 0x11
h.G *= 0x11
h.B *= 0x11
h.A *= 0x11
}
return nil
}
// MarshalText marshals the HexColor into a small string.
func (h HexColor) MarshalText() (text []byte, err error) {
return []byte(h.String()), nil
}
// String prints the HexColor as an abbreviated notation.
// Specifically, short form is used when possible (i.e., when all components are evenly divisible by 0x11).
// The alpha component is left out if it's 0xFF.
func (h HexColor) String() string {
if h.R%0x11 == 0 && h.G%0x11 == 0 && h.B%0x11 == 0 && h.A%0x11 == 0 {
// Short form works.
if h.A == 0xFF {
// It's great when it's easy!
return fmt.Sprintf("#%01X%01X%01X", h.R/0x11, h.G/0x11, h.B/0x11)
} else {
// Just need to add the alpha.
return fmt.Sprintf("#%01X%01X%01X%01X", h.R/0x11, h.G/0x11, h.B/0x11, h.A/0x11)
}
} else {
// Gotta use long form.
if h.A == 0xFF {
// Can skip the alpha channel, though.
return fmt.Sprintf("#%02X%02X%02X", h.R, h.G, h.B)
} else {
// Doing things the hard way.
return fmt.Sprintf("#%02X%02X%02X%02X", h.R, h.G, h.B, h.A)
}
}
}

@ -0,0 +1,83 @@
package state
import (
"fmt"
"github.com/rs/xid"
)
// HexMapRepresentation combines HexOrientation and LineParity to represent a map's display mode.
type HexMapRepresentation struct {
Orientation HexOrientation `json:"orientation"`
IndentedLines LineParity `json:"indentedLines"`
}
// HexCell contains data for a single cell of the map.
type HexCell struct {
// Color contains the color of the cell, in hex notation.
Color HexColor `json:"color"`
}
// HexLine is a line of cells which are adjacent by flat sides in a vertical or horizontal direction.
type HexLine []HexCell
// Copy creates a deep copy of this HexLine.
func (l HexLine) Copy() HexLine {
duplicate := make(HexLine, len(l))
for index, value := range l {
duplicate[index] = value
}
return duplicate
}
// HexLayer is a two-dimensional plane of cells which are arranged into lines.
type HexLayer []HexLine
// GetCellAt returns a reference to the cell at the given coordinates.
func (l HexLayer) GetCellAt(c StorageCoordinates) (*HexCell, error) {
if int(c.Line) > len(l) {
return nil, fmt.Errorf("line %d out of bounds (%d)", c.Line, len(l))
}
line := l[c.Line]
if int(c.Cell) > len(line) {
return nil, fmt.Errorf("cell %d out of bounds (%d)", c.Cell, len(line))
}
return &(line[c.Cell]), nil
}
// Copy creates a deep copy of this HexLayer.
func (l HexLayer) Copy() HexLayer {
duplicate := make(HexLayer, len(l))
for index, value := range l {
duplicate[index] = value.Copy()
}
return duplicate
}
// HexMap contains the data for a map instance.
type HexMap struct {
// Xid is the unique ID of the HexMap, used to encourage clients not to blindly interact with a different map.
Xid xid.ID `json:"xid"`
// Lines is the rough number of rows (in PointyTop orientation) or columns (in FlatTop orientation) in the map.
// Because different lines will be staggered, it's somewhat hard to see.
Lines uint8 `json:"lines"`
// CellsPerLine is the rough number of columns (in PointyTop orientation) or rows (in FlatTop orientation).
// This is the number of cells joined together, flat-edge to flat-edge, in each line.
CellsPerLine uint8 `json:"cellsPerLine"`
// DisplayMode is the orientation and line parity used to display the map.
DisplayMode HexMapRepresentation `json:"displayMode"`
// LineCells contains the actual map data.
// LineCells itself is a slice with Lines elements, each of which is a line;
// each of those lines is a slice of CellsPerLine cells.
LineCells HexLayer `json:"lineCells"`
}
// Copy creates a deep copy of this HexMap.
func (m HexMap) Copy() HexMap {
return HexMap{
Xid: m.Xid,
Lines: m.Lines,
CellsPerLine: m.CellsPerLine,
DisplayMode: m.DisplayMode,
LineCells: m.LineCells.Copy(),
}
}

@ -0,0 +1,51 @@
package state
import "fmt"
// HexOrientation is the enum for the direction hexes are facing.
type HexOrientation uint8
const (
// PointyTop indicates hexes that have a pair of sides on either side in the horizontal direction,
// and points on the top and bottom in the vertical direction.
PointyTop HexOrientation = 1
// FlatTop indicates hexes that have a pair of points on either side in the horizontal direction,
// and sides on the top and bottom in the vertical direction.
FlatTop HexOrientation = 2
)
// UnmarshalText unmarshals from the equivalent Javascript constant name.
func (o *HexOrientation) UnmarshalText(text []byte) error {
switch string(text) {
case "POINTY_TOP":
*o = PointyTop
return nil
case "FLAT_TOP":
*o = FlatTop
return nil
default:
return fmt.Errorf("can't unmarshal unknown HexOrientation %s", text)
}
}
// MarshalText marshals into the equivalent JavaScript constant name.
func (o HexOrientation) MarshalText() (text []byte, err error) {
switch o {
case PointyTop, FlatTop:
return []byte(o.String()), nil
default:
return nil, fmt.Errorf("can't marshal unknown HexOrientation %d", o)
}
}
// String returns the equivalent JavaScript constant name.
func (o HexOrientation) String() string {
switch o {
case PointyTop:
return "POINTY_TOP"
case FlatTop:
return "FLAT_TOP"
default:
return fmt.Sprintf("[unknown HexOrientation %d]", o)
}
}

@ -0,0 +1,49 @@
package state
import "fmt"
// LineParity indicates whether odd or even lines are indented.
type LineParity uint8
const (
// OddLines indicates that odd lines - 1, 3, 5... - are indented by 1/2 cell.
OddLines LineParity = 1
// EvenLines indicates that even lines - 0, 2, 4... - are indented by 1/2 cell.
EvenLines LineParity = 2
)
// UnmarshalText unmarshals from the equivalent Javascript constant name.
func (o *LineParity) UnmarshalText(text []byte) error {
switch string(text) {
case "ODD":
*o = OddLines
return nil
case "EVEN":
*o = EvenLines
return nil
default:
return fmt.Errorf("can't unmarshal unknown LineParity %s", text)
}
}
// MarshalText marshals into the equivalent JavaScript constant name.
func (o LineParity) MarshalText() (text []byte, err error) {
switch o {
case OddLines, EvenLines:
return []byte(o.String()), nil
default:
return nil, fmt.Errorf("can't marshal unknown LineParity %d", o)
}
}
// String returns the equivalent JavaScript constant name.
func (o LineParity) String() string {
switch o {
case OddLines:
return "ODD_LINES"
case EvenLines:
return "EVEN_LINES"
default:
return fmt.Sprintf("[unknown LineParity %d]", o)
}
}

@ -0,0 +1,15 @@
package state
// Synced contains all state that is synced between the server and its clients.
type Synced struct {
Map HexMap `json:"map"`
User UserData `json:"user"`
}
// Copy creates a deep copy of this Synced instance.
func (s Synced) Copy() Synced {
return Synced{
Map: s.Map.Copy(),
User: s.User.Copy(),
}
}

@ -0,0 +1,14 @@
package state
// UserData contains data about clients that is synced between client and server.
// Unlike the map, UserData is not persisted to disk, and all UserData is lost on shutdown.
type UserData struct {
ActiveColor HexColor `json:"active_color"`
}
// Copy creates a deep copy of this UserData.
func (u UserData) Copy() UserData {
return UserData{
ActiveColor: u.ActiveColor,
}
}
Loading…
Cancel
Save