diff --git a/README.md b/README.md
index 69ad522..2a3d611 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ Video game console operating system that displays on a 16*9 RGB LED matrix.
|
| Device | The Device software allows you to change the luminosity of the LEDs. |
|
|
| Blocks | A puzzle game, score a maximum of points by clearing complete lines. |
|
|
| Getout | A labyrinth game, try to get out if you can. |
|
-|
| Rollup dice | Random dice generator (https://github.com/gwenker/matrix-rollup-dice). |
|
+|
| Rollup dice | Roll two dice, press A for a new throw. |
|
|
| Animate | Player for animations generated with Glediator (http://www.solderlab.de/index.php/software/glediator). |
|
|
| Light | Simple software to generate mood light. |
|
diff --git a/docs/install.md b/docs/install.md
index 2a0bca1..7623024 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -25,7 +25,7 @@ That puts a static ARMv7 binary at `/usr/bin/matrix`, the data it renders from u
Choose what it runs in `/etc/default/matrix`:
```sh
-MATRIX_COMPONENTS="core device gamepad emulator demo zigzag yumyum clock draw blocks getout animate light"
+MATRIX_COMPONENTS="core device gamepad emulator demo zigzag yumyum clock draw blocks getout animate light rollupdice"
MATRIX_GAMEPAD_PORT=80
MATRIX_LOG_LEVEL=info
```
diff --git a/fonts/diceValues.json b/fonts/diceValues.json
new file mode 100644
index 0000000..dae2d06
--- /dev/null
+++ b/fonts/diceValues.json
@@ -0,0 +1,12 @@
+{
+ "name": "DiceValues",
+ "height": 5,
+ "caracters": {
+ "1": {"width": 5, "mask": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]},
+ "2": {"width": 5, "mask": [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]},
+ "3": {"width": 5, "mask": [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]},
+ "4": {"width": 5, "mask": [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1]},
+ "5": {"width": 5, "mask": [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1]},
+ "6": {"width": 5, "mask": [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1]}
+ }
+}
diff --git a/images/rollupdice.json b/images/rollupdice.json
new file mode 100644
index 0000000..569c53b
--- /dev/null
+++ b/images/rollupdice.json
@@ -0,0 +1,19 @@
+{
+ "name": "rollupdice",
+ "height": 7,
+ "width": 12,
+ "colors": [
+ { "r": 0, "g": 0, "b": 0, "a": 0 },
+ { "r": 255, "g": 255, "b": 255, "a": 1 },
+ { "r": 0, "g": 0, "b": 0, "a": 1 }
+ ],
+ "mask": [
+ 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1,
+ 1, 2, 1, 1, 1, 0, 0, 1, 2, 1, 2, 1,
+ 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 2, 1,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1
+ ]
+}
diff --git a/main.go b/main.go
index 9303319..0fde98e 100644
--- a/main.go
+++ b/main.go
@@ -19,6 +19,7 @@ import (
"github.com/richardlt/matrix/getout"
"github.com/richardlt/matrix/internal/errors"
"github.com/richardlt/matrix/light"
+ "github.com/richardlt/matrix/rollupdice"
"github.com/richardlt/matrix/yumyum"
"github.com/richardlt/matrix/zigzag"
)
@@ -51,7 +52,7 @@ func main() {
Usage: "[panic fatal error warning info debug]",
},
},
- ArgsUsage: "[core emulator gamepad device zigzag yumyum demo clock draw blocks]",
+ ArgsUsage: "[core emulator gamepad device zigzag yumyum demo clock draw blocks getout animate light rollupdice]",
Action: startAction,
}},
}
@@ -112,6 +113,8 @@ func startAction(ctx context.Context, cmd *cli.Command) error {
cs = append(cs, component(func() error { return animate.Start(cmd.String("core-uri")) }))
case "light":
cs = append(cs, component(func() error { return light.Start(cmd.String("core-uri")) }))
+ case "rollupdice":
+ cs = append(cs, component(func() error { return rollupdice.Start(cmd.String("core-uri")) }))
default:
return errors.Errorf("invalid component name %q, expected one of %s", arg, cmd.ArgsUsage)
}
diff --git a/packaging/deb/default b/packaging/deb/default
index db3929b..9291779 100644
--- a/packaging/deb/default
+++ b/packaging/deb/default
@@ -6,7 +6,7 @@
#
# Every component is a separate process-worth of goroutines and one gRPC connection, so
# on a small board it is worth trimming this to what you actually play.
-MATRIX_COMPONENTS="core device gamepad emulator demo zigzag yumyum clock draw blocks getout animate light"
+MATRIX_COMPONENTS="core device gamepad emulator demo zigzag yumyum clock draw blocks getout animate light rollupdice"
# Port for the gamepad web app, the virtual controller played from a phone.
MATRIX_GAMEPAD_PORT=80
diff --git a/rollupdice/daemon.go b/rollupdice/daemon.go
new file mode 100644
index 0000000..4b15ce9
--- /dev/null
+++ b/rollupdice/daemon.go
@@ -0,0 +1,57 @@
+package rollupdice
+
+import (
+ "github.com/sirupsen/logrus"
+
+ "github.com/richardlt/matrix/sdk-go/common"
+ "github.com/richardlt/matrix/sdk-go/software"
+)
+
+// Start the rollup dice software.
+func Start(uri string) error {
+ logrus.Infof("Start rollup dice for uri %s\n", uri)
+
+ r := &rollupDice{}
+
+ return software.Connect(uri, r, true)
+}
+
+type rollupDice struct {
+ engine *engine
+ renderer *renderer
+}
+
+func (r *rollupDice) Init(a software.API) (err error) {
+ logrus.Debug("Init rollup dice")
+
+ r.renderer, err = newRenderer(a)
+ if err != nil {
+ return err
+ }
+
+ if err := a.SetConfig(&software.ConnectRequest_SoftwareData_Config{
+ Logo: a.GetImageFromLocal("rollupdice"),
+ MinPlayerCount: 1,
+ MaxPlayerCount: 1,
+ }); err != nil {
+ return err
+ }
+
+ return a.Ready()
+}
+
+func (r *rollupDice) Start(uint64) {
+ r.engine = newEngine()
+ r.engine.roll()
+ r.renderer.print(r.engine.values)
+}
+
+func (r *rollupDice) Close() { r.renderer.clean() }
+
+func (r *rollupDice) ActionReceived(slot uint64, cmd common.Command) {
+ if cmd != common.Command_A_UP {
+ return
+ }
+ r.engine.roll()
+ r.renderer.print(r.engine.values)
+}
diff --git a/rollupdice/engine.go b/rollupdice/engine.go
new file mode 100644
index 0000000..0aec432
--- /dev/null
+++ b/rollupdice/engine.go
@@ -0,0 +1,21 @@
+package rollupdice
+
+import "math/rand"
+
+// diceCount dice are rolled together, one per half of the display.
+const diceCount = 2
+
+func newEngine() *engine { return &engine{} }
+
+type engine struct {
+ values [diceCount]int
+}
+
+// roll draws a new face, one to six, for every die. The global source is used rather than
+// one seeded from the clock: the board this runs on has no clock to seed from, and the
+// runtime seeds the global source itself.
+func (e *engine) roll() {
+ for i := range e.values {
+ e.values[i] = rand.Intn(6) + 1
+ }
+}
diff --git a/rollupdice/engine_test.go b/rollupdice/engine_test.go
new file mode 100644
index 0000000..3348680
--- /dev/null
+++ b/rollupdice/engine_test.go
@@ -0,0 +1,27 @@
+package rollupdice
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// Only the six faces of the DiceValues font can be rendered, so a value outside one to
+// six would print an empty face rather than a die.
+func TestRollStaysOnTheFaces(t *testing.T) {
+ assert := assert.New(t)
+
+ e := newEngine()
+ seen := map[int]bool{}
+
+ for i := 0; i < 1000; i++ {
+ e.roll()
+ for _, v := range e.values {
+ assert.GreaterOrEqual(v, 1)
+ assert.LessOrEqual(v, 6)
+ seen[v] = true
+ }
+ }
+
+ assert.Len(seen, 6)
+}
diff --git a/rollupdice/renderer.go b/rollupdice/renderer.go
new file mode 100644
index 0000000..a24b6d9
--- /dev/null
+++ b/rollupdice/renderer.go
@@ -0,0 +1,71 @@
+package rollupdice
+
+import (
+ "github.com/richardlt/matrix/sdk-go/common"
+ "github.com/richardlt/matrix/sdk-go/software"
+)
+
+// A face is a square of this many pixels, which is what fits two dice on the 16x9
+// display: a one pixel border all around and a two pixel gap down the middle.
+const faceSize = 7
+
+func newRenderer(a software.API) (*renderer, error) {
+ faceLayer, err := a.NewLayer()
+ if err != nil {
+ return nil, err
+ }
+
+ pipLayer, err := a.NewLayer()
+ if err != nil {
+ return nil, err
+ }
+
+ cd, err := pipLayer.NewCaracterDriver(a.GetFontFromLocal("DiceValues"))
+ if err != nil {
+ return nil, err
+ }
+
+ return &renderer{
+ api: a,
+ faceLayer: faceLayer,
+ pipLayer: pipLayer,
+ caracterDriver: cd,
+ faceColor: &common.Color{R: 255, G: 255, B: 255, A: 1},
+ pipColor: &common.Color{A: 1},
+ }, nil
+}
+
+type renderer struct {
+ api software.API
+ faceLayer, pipLayer software.Layer
+ caracterDriver *software.CaracterDriver
+ faceColor, pipColor *common.Color
+}
+
+func (r *renderer) clean() {
+ _ = r.faceLayer.Clean()
+ _ = r.pipLayer.Clean()
+}
+
+func (r *renderer) print(values [diceCount]int) {
+ r.clean()
+
+ for i, value := range values {
+ originX := i * (faceSize + 2)
+
+ for x := originX; x < originX+faceSize; x++ {
+ for y := 1; y <= faceSize; y++ {
+ _ = r.faceLayer.SetWithCoord(&common.Coord{X: int64(x), Y: int64(y)}, r.faceColor)
+ }
+ }
+
+ // The pips of a value are one caracter of the DiceValues font, five by five, and
+ // the driver places it around its center. The background is left transparent so
+ // the face below shows through.
+ _ = r.caracterDriver.Render(rune('0'+value),
+ &common.Coord{X: int64(originX + faceSize/2), Y: int64(1 + faceSize/2)},
+ r.pipColor, &common.Color{})
+ }
+
+ _ = r.api.Print()
+}
diff --git a/scripts/prepare-alpine-card.sh b/scripts/prepare-alpine-card.sh
index 0734849..83b54f9 100755
--- a/scripts/prepare-alpine-card.sh
+++ b/scripts/prepare-alpine-card.sh
@@ -46,7 +46,7 @@ DEBUG=0
# core and device, plus every game. gamepad and emulator are left out: gamepad is the web
# controller, unreachable without a network, and emulator is a development tool. Trim this
# with --components if memory gets tight.
-COMPONENTS="core device demo zigzag yumyum clock draw blocks getout animate light"
+COMPONENTS="core device demo zigzag yumyum clock draw blocks getout animate light rollupdice"
# Go's collector shares the single core with rendering, and the frame path allocates on
# every frame, so the default collection rate shows up as a periodic stutter. Trading
# memory for fewer collections is the right way round on a board that does nothing else.