parent
443a47b387
commit
abaeb8d609
@ -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 @@ |
|||||||
|
package server |
@ -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…
Reference in new issue