diff --git a/.idea/runConfigurations/All_Tests.xml b/.idea/runConfigurations/All_Tests.xml new file mode 100644 index 0000000..590bba1 --- /dev/null +++ b/.idea/runConfigurations/All_Tests.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Client_Tests.xml b/.idea/runConfigurations/Client_Tests.xml new file mode 100644 index 0000000..9d5a727 --- /dev/null +++ b/.idea/runConfigurations/Client_Tests.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Server_Tests.xml b/.idea/runConfigurations/Server_Tests.xml new file mode 100644 index 0000000..57ec7b4 --- /dev/null +++ b/.idea/runConfigurations/Server_Tests.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/mage_protobuf_build.xml b/.idea/runConfigurations/mage_protobuf_build.xml new file mode 100644 index 0000000..22b93e2 --- /dev/null +++ b/.idea/runConfigurations/mage_protobuf_build.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/.idea/webResources.xml b/.idea/webResources.xml new file mode 100644 index 0000000..a73f76a --- /dev/null +++ b/.idea/webResources.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/App.test.tsx b/client/src/App.test.tsx index 2a68616..58fc597 100644 --- a/client/src/App.test.tsx +++ b/client/src/App.test.tsx @@ -2,8 +2,10 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import App from './App'; +/* test('renders learn react link', () => { render(); const linkElement = screen.getByText(/learn react/i); expect(linkElement).toBeInTheDocument(); }); + */ diff --git a/mage.sh b/mage.sh index bc7122d..2d4a567 100755 --- a/mage.sh +++ b/mage.sh @@ -2,7 +2,7 @@ set -eux -PATH=${PATH}:${GOROOT}/bin +PATH=${GOROOT}/bin:${PATH} SCRIPTPATH=$(readlink -e "${BASH_SOURCE[0]}") MAINPATH=${SCRIPTPATH%/*} BUILDTOOLSPATH=${MAINPATH}/buildtools diff --git a/proto/client.proto b/proto/client.proto index f493448..12ca311 100644 --- a/proto/client.proto +++ b/proto/client.proto @@ -2,7 +2,7 @@ syntax = "proto3"; import "action.proto"; -option go_package = "git.reya.zone/reya/hexmap/server/websocket"; +option go_package = "git.reya.zone/reya/hexmap/server/ws"; message ClientHelloPB { uint32 version = 1; diff --git a/proto/server.proto b/proto/server.proto index 0cbb916..2cc3d0e 100644 --- a/proto/server.proto +++ b/proto/server.proto @@ -3,7 +3,7 @@ syntax = "proto3"; import "action.proto"; import "state.proto"; -option go_package = "git.reya.zone/reya/hexmap/server/websocket"; +option go_package = "git.reya.zone/reya/hexmap/server/ws"; message ServerHelloPB { uint32 version = 1; diff --git a/server/action/action.pbconv.go b/server/action/action.pbconv.go index 9a9dc20..3315d9e 100644 --- a/server/action/action.pbconv.go +++ b/server/action/action.pbconv.go @@ -38,7 +38,7 @@ func (x *CellSetColorPB) ToGo() (*CellColor, error) { } return &CellColor{ At: at, - Color: state.ColorFromInt(x.Color), + Color: state.ColorFromRGBA8888(x.Color), }, nil } @@ -50,7 +50,7 @@ func (c CellColor) ToClientPB() *ClientActionPB { return &ClientActionPB{ Action: &ClientActionPB_CellSetColor{ CellSetColor: &CellSetColorPB{ - Color: c.Color.ToInt(), + Color: c.Color.ToRGBA8888(), At: c.At.ToPB(), }, }, @@ -59,7 +59,7 @@ func (c CellColor) ToClientPB() *ClientActionPB { func (x *UserSetActiveColorPB) ToGo() *UserActiveColor { return &UserActiveColor{ - Color: state.ColorFromInt(x.Color), + Color: state.ColorFromRGBA8888(x.Color), } } @@ -71,7 +71,7 @@ func (c UserActiveColor) ToClientPB() *ClientActionPB { return &ClientActionPB{ Action: &ClientActionPB_UserSetActiveColor{ UserSetActiveColor: &UserSetActiveColorPB{ - Color: c.Color.ToInt(), + Color: c.Color.ToRGBA8888(), }, }, } diff --git a/server/action/action.pbconv_test.go b/server/action/action.pbconv_test.go new file mode 100644 index 0000000..d1b0483 --- /dev/null +++ b/server/action/action.pbconv_test.go @@ -0,0 +1,240 @@ +package action + +import ( + "git.reya.zone/reya/hexmap/server/state" + "google.golang.org/protobuf/runtime/protoimpl" + "reflect" + "testing" +) + +func TestCellColor_ToClientPB(t *testing.T) { + type fields struct { + At state.StorageCoordinates + Color state.Color + } + tests := []struct { + name string + fields fields + want *ClientActionPB + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := CellColor{ + At: tt.fields.At, + Color: tt.fields.Color, + } + if got := c.ToClientPB(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToClientPB() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCellColor_ToServerPB(t *testing.T) { + type fields struct { + At state.StorageCoordinates + Color state.Color + } + tests := []struct { + name string + fields fields + want *ServerActionPB + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := CellColor{ + At: tt.fields.At, + Color: tt.fields.Color, + } + if got := c.ToServerPB(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToServerPB() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCellSetColorPB_ToGo(t *testing.T) { + type fields struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Color uint32 + At *state.StorageCoordinatesPB + } + tests := []struct { + name string + fields fields + want *CellColor + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := &CellSetColorPB{ + state: tt.fields.state, + sizeCache: tt.fields.sizeCache, + unknownFields: tt.fields.unknownFields, + Color: tt.fields.Color, + At: tt.fields.At, + } + got, err := x.ToGo() + if (err != nil) != tt.wantErr { + t.Errorf("ToGo() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToGo() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestClientActionPB_ToGo(t *testing.T) { + type fields struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Action isClientActionPB_Action + } + tests := []struct { + name string + fields fields + want Client + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := &ClientActionPB{ + state: tt.fields.state, + sizeCache: tt.fields.sizeCache, + unknownFields: tt.fields.unknownFields, + Action: tt.fields.Action, + } + got, err := x.ToGo() + if (err != nil) != tt.wantErr { + t.Errorf("ToGo() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToGo() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestServerActionPB_ToGo(t *testing.T) { + type fields struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Action isServerActionPB_Action + } + tests := []struct { + name string + fields fields + want Server + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := &ServerActionPB{ + state: tt.fields.state, + sizeCache: tt.fields.sizeCache, + unknownFields: tt.fields.unknownFields, + Action: tt.fields.Action, + } + got, err := x.ToGo() + if (err != nil) != tt.wantErr { + t.Errorf("ToGo() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToGo() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUserActiveColor_ToClientPB(t *testing.T) { + type fields struct { + Color state.Color + } + tests := []struct { + name string + fields fields + want *ClientActionPB + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := UserActiveColor{ + Color: tt.fields.Color, + } + if got := c.ToClientPB(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToClientPB() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUserActiveColor_ToServerPB(t *testing.T) { + type fields struct { + Color state.Color + } + tests := []struct { + name string + fields fields + want *ServerActionPB + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := UserActiveColor{ + Color: tt.fields.Color, + } + if got := c.ToServerPB(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToServerPB() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUserSetActiveColorPB_ToGo(t *testing.T) { + type fields struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Color uint32 + } + tests := []struct { + name string + fields fields + want *UserActiveColor + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + x := &UserSetActiveColorPB{ + state: tt.fields.state, + sizeCache: tt.fields.sizeCache, + unknownFields: tt.fields.unknownFields, + Color: tt.fields.Color, + } + if got := x.ToGo(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToGo() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/server/action/action_test.go b/server/action/action_test.go new file mode 100644 index 0000000..466b30b --- /dev/null +++ b/server/action/action_test.go @@ -0,0 +1,190 @@ +package action + +import ( + "git.reya.zone/reya/hexmap/server/state" + "go.uber.org/zap/zapcore" + "reflect" + "testing" +) + +func TestCellColor_Apply(t *testing.T) { + type fields struct { + At state.StorageCoordinates + Color state.Color + } + type args struct { + s *state.Synced + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := CellColor{ + At: tt.fields.At, + Color: tt.fields.Color, + } + if err := c.Apply(tt.args.s); (err != nil) != tt.wantErr { + t.Errorf("Apply() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCellColor_MarshalLogObject(t *testing.T) { + type fields struct { + At state.StorageCoordinates + Color state.Color + } + type args struct { + encoder zapcore.ObjectEncoder + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := CellColor{ + At: tt.fields.At, + Color: tt.fields.Color, + } + if err := c.MarshalLogObject(tt.args.encoder); (err != nil) != tt.wantErr { + t.Errorf("MarshalLogObject() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestIDed_MarshalLogObject(t *testing.T) { + type fields struct { + ID uint32 + Action Client + } + type args struct { + encoder zapcore.ObjectEncoder + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + i := IDed{ + ID: tt.fields.ID, + Action: tt.fields.Action, + } + if err := i.MarshalLogObject(tt.args.encoder); (err != nil) != tt.wantErr { + t.Errorf("MarshalLogObject() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestServerSlice_MarshalLogArray(t *testing.T) { + type args struct { + encoder zapcore.ArrayEncoder + } + tests := []struct { + name string + s ServerSlice + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.MarshalLogArray(tt.args.encoder); (err != nil) != tt.wantErr { + t.Errorf("MarshalLogArray() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestUserActiveColor_Apply(t *testing.T) { + type fields struct { + Color state.Color + } + type args struct { + s *state.Synced + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := UserActiveColor{ + Color: tt.fields.Color, + } + if err := c.Apply(tt.args.s); (err != nil) != tt.wantErr { + t.Errorf("Apply() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestUserActiveColor_MarshalLogObject(t *testing.T) { + type fields struct { + Color state.Color + } + type args struct { + encoder zapcore.ObjectEncoder + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := UserActiveColor{ + Color: tt.fields.Color, + } + if err := c.MarshalLogObject(tt.args.encoder); (err != nil) != tt.wantErr { + t.Errorf("MarshalLogObject() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_serverPBFromClient(t *testing.T) { + type args struct { + c Client + } + tests := []struct { + name string + args args + want *ServerActionPB + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := serverPBFromClient(tt.args.c); !reflect.DeepEqual(got, tt.want) { + t.Errorf("serverPBFromClient() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/server/state/color.go b/server/state/color.go index fdab1cb..1261406 100644 --- a/server/state/color.go +++ b/server/state/color.go @@ -1,14 +1,13 @@ package state -import "fmt" +import ( + "errors" + "fmt" + "strconv" + "strings" +) -// Color 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. +// Color is an internal representation of an RGBA8888 color. type Color struct { // R is the red component of the color. R uint8 @@ -20,7 +19,33 @@ type Color struct { A uint8 } -// String prints the Color as an abbreviated notation. +var TransparentColor = Color{R: 0, G: 0, B: 0, A: 0} +var ErrInvalidColorString = errors.New("color strings must start with # and be followed by 3, 4, 6, or 8 hex digits") + +func ColorFromString(s string) (Color, error) { + if !strings.HasPrefix(s, "#") { + return TransparentColor, ErrInvalidColorString + } + hex := s[1:] + v, err := strconv.ParseUint(hex, 16, 64) + if err != nil { + return TransparentColor, ErrInvalidColorString + } + switch len(hex) { + case 3: + return ColorFromRGBA4444(uint16(v<<4 | 0xF)), nil + case 4: + return ColorFromRGBA4444(uint16(v)), nil + case 6: + return ColorFromRGBA8888(uint32(v<<8 | 0xFF)), nil + case 8: + return ColorFromRGBA8888(uint32(v)), nil + default: + return TransparentColor, ErrInvalidColorString + } +} + +// String prints the Color in 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 (c Color) String() string { diff --git a/server/state/color.pbconv.go b/server/state/color.pbconv.go index 4549633..8c38889 100644 --- a/server/state/color.pbconv.go +++ b/server/state/color.pbconv.go @@ -1,7 +1,7 @@ package state -// ColorFromInt decodes a packed uint32 into a hex color. -func ColorFromInt(value uint32) Color { +// ColorFromRGBA8888 decodes a packed uint32 (RGBA8888) into a hex color. +func ColorFromRGBA8888(value uint32) Color { return Color{ R: uint8((value >> 24) & 0xFF), G: uint8((value >> 16) & 0xFF), @@ -10,7 +10,22 @@ func ColorFromInt(value uint32) Color { } } -// ToInt packs a hex color into a uint32. -func (c Color) ToInt() uint32 { +// ColorFromRGBA4444 decodes a packed uint16 (RGBA4444) into a hex color. +func ColorFromRGBA4444(value uint16) Color { + return Color{ + R: uint8((value>>12)&0xF) * 0x11, + G: uint8((value>>8)&0xF) * 0x11, + B: uint8((value>>4)&0xF) * 0x11, + A: uint8((value>>0)&0xF) * 0x11, + } +} + +// ToRGBA8888 packs a hex color into a uint32 as RGBA8888. +func (c Color) ToRGBA8888() uint32 { return uint32(c.R)<<24 | uint32(c.G)<<16 | uint32(c.B)<<8 | uint32(c.A) } + +// ToRGBA4444 packs a hex color into a uint16 as RGBA4444. +func (c Color) ToRGBA4444() uint16 { + return uint16((c.R>>4)&0xF)<<12 | uint16((c.G>>4)&0xF)<<8 | uint16((c.B>>4)&0xF)<<4 | uint16((c.A>>4)&0xF) +} diff --git a/server/state/map.pbconv.go b/server/state/map.pbconv.go index 2afdab2..9c55190 100644 --- a/server/state/map.pbconv.go +++ b/server/state/map.pbconv.go @@ -65,13 +65,13 @@ func (l Layout) ToPB() *HexMapPB_Layout { func (x *HexCellPB) ToGo() HexCell { return HexCell{ - Color: ColorFromInt(x.Color), + Color: ColorFromRGBA8888(x.Color), } } func (h HexCell) ToPB() *HexCellPB { return &HexCellPB{ - Color: h.Color.ToInt(), + Color: h.Color.ToRGBA8888(), } } diff --git a/server/state/user.pbconv.go b/server/state/user.pbconv.go index be9a135..0450dbb 100644 --- a/server/state/user.pbconv.go +++ b/server/state/user.pbconv.go @@ -2,12 +2,12 @@ package state func (x *UserStatePB) ToGo() UserState { return UserState{ - ActiveColor: ColorFromInt(x.Color), + ActiveColor: ColorFromRGBA8888(x.Color), } } func (u UserState) ToPB() *UserStatePB { return &UserStatePB{ - Color: u.ActiveColor.ToInt(), + Color: u.ActiveColor.ToRGBA8888(), } }