Version 1.0
This commit is contained in:
parent
3d58303936
commit
3871273730
11 changed files with 197 additions and 33 deletions
12
Dockerfile
Normal file
12
Dockerfile
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
FROM golang:1.16.2-alpine AS builder
|
||||||
|
RUN apk update
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . /app
|
||||||
|
RUN GOOS=linux go build -o kartograph cmd/kartograph-map-generator/main.go
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
RUN apk update
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/kartograph /app/
|
||||||
|
EXPOSE 80
|
||||||
|
ENTRYPOINT ["/app/kartograph"]
|
34
README.mkd
Normal file
34
README.mkd
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# Der Kartograph Map Generator
|
||||||
|
|
||||||
|
Generate your own maps for "Der Kartograph" easily.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
You need to build the source first. The simplest way is to use the
|
||||||
|
provided Dockerfile, which will build the software and create a
|
||||||
|
convenient image (This is a multi stage Dockerfile, so the resulting
|
||||||
|
image is a small Alpine Linux image with ust the map generator binary.)
|
||||||
|
|
||||||
|
$ docker build -t kartograph .
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
See all available options:
|
||||||
|
|
||||||
|
$ docker run -it kartograph -help
|
||||||
|
|
||||||
|
Spawn a webserver:
|
||||||
|
|
||||||
|
$ docker run -it kartograph -web
|
||||||
|
|
||||||
|
Generate a map with the seed "hey jim" and output it as a SVG image:
|
||||||
|
|
||||||
|
$ docker run -it kartograph -output=svg -seed='hey jim' > map.svg
|
||||||
|
|
||||||
|
You want an epic game? Lets generate a big map with a lot of wasteland
|
||||||
|
and ruins:
|
||||||
|
|
||||||
|
$ docker run -it kartograph -seed=aaron \
|
||||||
|
-size=20 -wastelands=100 -ruins=10 \
|
||||||
|
-output=svg > map.svg
|
||||||
|
|
|
@ -45,7 +45,7 @@ func main() {
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&startWebserver, "web", false, "Spawn a webserver")
|
flag.BoolVar(&startWebserver, "web", false, "Spawn a webserver")
|
||||||
flag.StringVar(&address, "address", "0.0.0.0", "IP to bin the service to")
|
flag.StringVar(&address, "address", "0.0.0.0", "IP to bind the service to")
|
||||||
flag.IntVar(&port, "port", 80, "Port to bind the service to")
|
flag.IntVar(&port, "port", 80, "Port to bind the service to")
|
||||||
|
|
||||||
flag.StringVar(&output, "output", "svg", "Output format (svg, ascii, json)")
|
flag.StringVar(&output, "output", "svg", "Output format (svg, ascii, json)")
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -4,5 +4,4 @@ go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ajstarks/svgo v0.0.0-20200725142600-7a3c8b57fecb
|
github.com/ajstarks/svgo v0.0.0-20200725142600-7a3c8b57fecb
|
||||||
github.com/jung-kurt/gofpdf v1.16.2
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
package generator
|
|
||||||
|
|
||||||
import ()
|
|
||||||
|
|
||||||
func (w World) PDF() {
|
|
||||||
|
|
||||||
}
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TerritoryType int
|
type TerritoryType int
|
||||||
|
@ -69,7 +70,7 @@ type World struct {
|
||||||
Mountains int `json:"mountains"`
|
Mountains int `json:"mountains"`
|
||||||
|
|
||||||
World []Tile `json:"tiles"`
|
World []Tile `json:"tiles"`
|
||||||
Seed int
|
Seed string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w World) Plot() string {
|
func (w World) Plot() string {
|
||||||
|
@ -90,23 +91,15 @@ func (w World) JSON() string {
|
||||||
return string(output)
|
return string(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w World) ExportToPDF(filename string) error {
|
|
||||||
// TODO: Draw the PDF
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(size int, numWastelands int, numMountains int, numRuins int, seed string) World {
|
func New(size int, numWastelands int, numMountains int, numRuins int, seed string) World {
|
||||||
// Fix seed
|
InitSeed(seed)
|
||||||
h := md5.New()
|
|
||||||
io.WriteString(h, seed)
|
|
||||||
var intSeed uint64 = binary.BigEndian.Uint64(h.Sum(nil))
|
|
||||||
rand.Seed(int64(intSeed))
|
|
||||||
|
|
||||||
w := World{
|
w := World{
|
||||||
Size: size,
|
Size: size,
|
||||||
Wastelands: numWastelands,
|
Wastelands: numWastelands,
|
||||||
Mountains: numMountains,
|
Mountains: numMountains,
|
||||||
Ruins: numRuins,
|
Ruins: numRuins,
|
||||||
|
Seed: seed,
|
||||||
}
|
}
|
||||||
|
|
||||||
// All empty for start
|
// All empty for start
|
||||||
|
@ -174,6 +167,24 @@ func New(size int, numWastelands int, numMountains int, numRuins int, seed strin
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InitSeed(seed string) {
|
||||||
|
h := md5.New()
|
||||||
|
io.WriteString(h, seed)
|
||||||
|
var intSeed uint64 = binary.BigEndian.Uint64(h.Sum(nil))
|
||||||
|
rand.Seed(int64(intSeed))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RandomSeed() string {
|
||||||
|
InitSeed(time.Now().String())
|
||||||
|
chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
var seed []byte
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
seed = append(seed, chars[roll(25)])
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(seed)
|
||||||
|
}
|
||||||
|
|
||||||
func (w World) neighbour(direction int, pos int) (int, error) {
|
func (w World) neighbour(direction int, pos int) (int, error) {
|
||||||
switch direction {
|
switch direction {
|
||||||
case UpDirection:
|
case UpDirection:
|
||||||
|
|
|
@ -41,15 +41,15 @@ func mapHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
if seed == "" {
|
if seed == "" {
|
||||||
seed = time.Now().String()
|
seed = time.Now().String()
|
||||||
}
|
}
|
||||||
wastelands, err := strconv.Atoi(req.URL.Query().Get("wastelands"))
|
wastelands, err := strconv.Atoi(req.URL.Query().Get("w"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wastelands = 7
|
wastelands = 7
|
||||||
}
|
}
|
||||||
mountains, err := strconv.Atoi(req.URL.Query().Get("mountains"))
|
mountains, err := strconv.Atoi(req.URL.Query().Get("m"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mountains = 5
|
mountains = 5
|
||||||
}
|
}
|
||||||
ruins, err := strconv.Atoi(req.URL.Query().Get("ruins"))
|
ruins, err := strconv.Atoi(req.URL.Query().Get("r"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ruins = 6
|
ruins = 6
|
||||||
}
|
}
|
||||||
|
@ -63,12 +63,15 @@ func mapHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexHandler(w http.ResponseWriter, req *http.Request) {
|
func indexHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
log.Printf("GET / (%v)", req.RemoteAddr)
|
seed := generator.RandomSeed()
|
||||||
|
world := generator.New(11, 7, 5, 6, seed)
|
||||||
|
|
||||||
|
log.Printf("GET / (%v) Seed: %v", req.RemoteAddr, seed)
|
||||||
|
|
||||||
tpl, err := template.ParseFS(templateFiles, "templates/index.tpl.html")
|
tpl, err := template.ParseFS(templateFiles, "templates/index.tpl.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tpl.Execute(w, nil)
|
tpl.Execute(w, world)
|
||||||
}
|
}
|
||||||
|
|
51
pkg/web/static/generator.js
Normal file
51
pkg/web/static/generator.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
let generate = function(seed) {
|
||||||
|
let size = document.querySelector('input[name="size"]').value
|
||||||
|
let wastelands = document.querySelector('input[name="wastelands"]').value
|
||||||
|
let mountains = document.querySelector('input[name="mountains"]').value
|
||||||
|
let ruins = document.querySelector('input[name="ruins"]').value
|
||||||
|
|
||||||
|
let map = document.querySelector('.map')
|
||||||
|
// Generate new random seed
|
||||||
|
let newMapUrl = '/map.svg?seed=' + seed +
|
||||||
|
'&s=' + size +
|
||||||
|
'&w=' + wastelands +
|
||||||
|
'&m=' + mountains +
|
||||||
|
'&r=' + ruins
|
||||||
|
document.querySelector('.map').setAttribute('src', newMapUrl);
|
||||||
|
document.querySelector('#map-link').setAttribute('href', newMapUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
let buttons = document.querySelectorAll('input[type="button"]')
|
||||||
|
Array.prototype.forEach.call(buttons, function(button, i) {
|
||||||
|
button.addEventListener('click', function(e) {
|
||||||
|
// Reset to default buttons
|
||||||
|
if (e.target.hasAttribute('data-default')) {
|
||||||
|
let defaultValue = e.target.getAttribute('data-default');
|
||||||
|
let name = e.target.getAttribute('name').substr(8);
|
||||||
|
let targetInput = document.querySelector('input[type="text"][name="'+name+'"]')
|
||||||
|
targetInput.value = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Roll the dice
|
||||||
|
if (e.target.getAttribute('name') === 'random-seed') {
|
||||||
|
let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
let seed = '';
|
||||||
|
for (let i=0; i<10; i++) {
|
||||||
|
let index = Math.floor(Math.random() * 25)
|
||||||
|
seed += chars[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
let seedElement = document.querySelector('input[name="seed"]');
|
||||||
|
seedElement.value = seed;
|
||||||
|
generate(seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh
|
||||||
|
if (e.target.getAttribute('name') === 'refresh') {
|
||||||
|
let seed = document.querySelector('input[name="seed"]').value;
|
||||||
|
generate(seed);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
Binary file not shown.
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 31 KiB |
|
@ -1,10 +1,38 @@
|
||||||
|
body {
|
||||||
|
width: 900px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
p > img {
|
p > img {
|
||||||
float: right;
|
float: left;
|
||||||
width: 250px;
|
width: 100px;
|
||||||
|
margin: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map {
|
.map {
|
||||||
margin: auto;
|
float: right;
|
||||||
display: block;
|
width: 450px;
|
||||||
width: 350px;
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
width: 100px;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
input[name="seed"] {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
width: 370px;
|
||||||
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,11 @@
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link href="/static/styles.css" rel="stylesheet" type="text/css" media="all">
|
<link href="/static/styles.css" rel="stylesheet" type="text/css" media="all">
|
||||||
|
<script src="/static/generator.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Map Generator für "Der Kartograph"</h1>
|
<h1>Map Generator für "Der Kartograph"</h1>
|
||||||
|
<a id="map-link" href="/map.svg?seed={{ .Seed }}&s={{ .Size }}&w={{ .Wastelands }}&r={{ .Ruins }}&m={{ .Mountains }}"><img src="/map.svg?seed={{ .Seed }}&s={{ .Size }}&w={{ .Wastelands }}&r={{ .Ruins }}&m={{ .Mountains }}" class="map"></a>
|
||||||
<p><img src="/static/images/logo.jpg">
|
<p><img src="/static/images/logo.jpg">
|
||||||
Wem der beigelegte Block zu eintönig wird oder schon leergespielt hat, kann sich
|
Wem der beigelegte Block zu eintönig wird oder schon leergespielt hat, kann sich
|
||||||
entweder die Mini-Erweiterung kaufen, in dem ein weiterer Block enthalten ist,
|
entweder die Mini-Erweiterung kaufen, in dem ein weiterer Block enthalten ist,
|
||||||
|
@ -17,8 +19,39 @@
|
||||||
Die generierten Karten entsprechen den erweiterten Regeln, können aber für etwas
|
Die generierten Karten entsprechen den erweiterten Regeln, können aber für etwas
|
||||||
mehr Spaß angepasst werden.</p>
|
mehr Spaß angepasst werden.</p>
|
||||||
|
|
||||||
<a href="/">Neue Map generieren</a>
|
{{ with . }}
|
||||||
<br><br>
|
<form>
|
||||||
<img src="/map.svg" class="map">
|
<fieldset>
|
||||||
|
<legend>Seed</legend>
|
||||||
|
<input type="text" name="seed" value="{{ .Seed }}">
|
||||||
|
<input type="button" name="random-seed" value="Zufall">
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Spielfeldgröße</legend>
|
||||||
|
<input type="text" name="size" value="{{ .Size }}">
|
||||||
|
<input type="button" name="default-size" value="Standard (11)" class="default-button" data-default="11">
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Parameter</legend>
|
||||||
|
<label for="wastelands">Ödland:</label>
|
||||||
|
<input type="text" name="wastelands" value="{{ .Wastelands }}">
|
||||||
|
<input type="button" name="default-wastelands" value="Standard (7)" class="default-button" data-default="7">
|
||||||
|
<br>
|
||||||
|
<label for="mountains">Berge:</label>
|
||||||
|
<input type="text" name="mountains" value="{{ .Mountains }}">
|
||||||
|
<input type="button" name="default-mountains" value="Standard (5)" class="default-button" data-default="5">
|
||||||
|
<br>
|
||||||
|
<label for="ruins">Ruinen:</label>
|
||||||
|
<input type="text" name="ruins" value="{{ .Ruins }}">
|
||||||
|
<input type="button" name="default-ruins" value="Standard (6)" class="default-button" data-default="6">
|
||||||
|
<br>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<input type="button" name="refresh" value="Neu generieren">
|
||||||
|
<input type="button" name="print" value="Drucken">
|
||||||
|
</form>
|
||||||
|
{{ end }}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue