Skip to content

Commit

Permalink
Initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
moul committed Nov 4, 2015
1 parent 241f0c4 commit 0229279
Show file tree
Hide file tree
Showing 2 changed files with 267 additions and 0 deletions.
34 changes: 34 additions & 0 deletions cmd/shikakugen/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"fmt"
"log"
"math/rand"
"time"

"github.com/jessevdk/go-flags"
"github.com/moul/shikaku"
)

func init() {
rand.Seed(time.Now().UTC().UnixNano())
}

func main() {
var opts struct {
Width int `short:"W" long:"width" description:"Width of the grid" required:"true"`
Height int `short:"H" long:"height" description:"Height of the grid" required:"true"`
Blocks int `short:"B" long:"blocks" description:"Blocks in the grid" required:"true"`
}

if _, err := flags.Parse(&opts); err != nil {
log.Fatalf("Parsing error: %v", err)
}

shikakuMap := shikaku.NewShikakuMap(opts.Width, opts.Height, 0, 0)
if err := shikakuMap.GenerateBlocks(opts.Blocks); err != nil {
log.Fatalf("Failed to generate %d blocks: %v", opts.Blocks, err)
}
fmt.Println(shikakuMap.String())
// fmt.Println(shikakuMap.Draw())
}
233 changes: 233 additions & 0 deletions shikakugen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package shikaku

import (
"fmt"
"math/rand"
"strings"
)

type ShikakuMap struct {
Width int
Height int
XPos int
YPos int
SubMaps []*ShikakuMap
Grid [][]int
RandXPos int
RandYPos int
}

func NewShikakuMap(width, height, xpos, ypos int) *ShikakuMap {
sMap := ShikakuMap{
Width: width,
Height: height,
XPos: xpos,
YPos: ypos,
RandXPos: rand.Intn(width),
RandYPos: rand.Intn(height),
}
sMap.Reset()
return &sMap
}

func (m *ShikakuMap) Reset() {
m.SubMaps = make([]*ShikakuMap, 0)
m.Grid = make([][]int, m.Height)
for i := 0; i < m.Height; i++ {
m.Grid[i] = make([]int, m.Width)
}
}

func (m *ShikakuMap) GenerateBlocks(amount int) error {
// first try to place N blocks
for generateAttempt := 0; generateAttempt < 10; generateAttempt++ {
hasError := false
for i := 0; i < amount; i++ {
addSucceed := false
for addAttempt := 0; addAttempt < 10; addAttempt++ {
width := 1
height := 1
xPos := rand.Intn(m.Width)
yPos := rand.Intn(m.Height)

block := NewShikakuMap(width, height, xPos, yPos)
for j := 0; j < 10; j++ {
biggerBlock := block.Grow()
if m.BlockFits(biggerBlock) {
block = biggerBlock
}
}

if block.Size() > 1 {
if err := m.AddBlock(block); err != nil {
panic(err)
}
addSucceed = true
break
}
}
if !addSucceed {
hasError = true
break
}
}
if !hasError {
// grow the existing blocks
for i := 0; i < 1000; i++ {
idx := rand.Intn(amount)
if err := m.TryToGrowBlock(idx); err == nil {
if m.AvailableSlots() == 0 {
return nil
}
}
}
}

m.Reset()
}

return fmt.Errorf("Failed to generate a map withing 10 attempts")
}

func (m *ShikakuMap) RemoveBlock(blockIdx int) error {
block := m.SubMaps[blockIdx]
m.SubMaps = append(m.SubMaps[:blockIdx], m.SubMaps[blockIdx+1:]...)
for y := 0; y < block.Height; y++ {
for x := 0; x < block.Width; x++ {
m.Grid[y+block.YPos][x+block.XPos]--
}
}
return nil
}

func (m *ShikakuMap) TryToGrowBlock(blockIdx int) error {
block := m.SubMaps[blockIdx]
newBlock := block.Grow()
if err := m.RemoveBlock(blockIdx); err != nil {
return err
}

if m.BlockFits(newBlock) {
m.AddBlock(newBlock)
return nil
}

// add back the old block
m.AddBlock(block)
return fmt.Errorf("nothing changed")
}

func (m *ShikakuMap) AvailableSlots() int {
available := 0
for y := 0; y < m.Height; y++ {
for x := 0; x < m.Width; x++ {
if m.Grid[y][x] == 0 {
available++
}
}
}
return available
}

func (m *ShikakuMap) BlockFits(block *ShikakuMap) bool {
if block.XPos < 0 || block.YPos < 0 {
return false
}
if block.XPos+block.Width > m.Width || block.YPos+block.Height > m.Height {
return false
}
for y := 0; y < block.Height; y++ {
for x := 0; x < block.Width; x++ {
if m.Grid[block.YPos+y][block.XPos+x] > 0 {
return false
}
}
}
return true
}

func (m *ShikakuMap) Grow() *ShikakuMap {
biggerMap := NewShikakuMap(m.Width, m.Height, m.XPos, m.YPos)
switch rand.Intn(4) {
case 0:
biggerMap.Width++
case 1:
biggerMap.XPos--
case 2:
biggerMap.Height++
case 3:
biggerMap.YPos--
}
return biggerMap
}

func (m *ShikakuMap) AddBlock(block *ShikakuMap) error {
if block.Size() < 2 {
return fmt.Errorf("Block too small")
}
if !m.BlockFits(block) {
return fmt.Errorf("The block does not fit in the current map")
}
m.SubMaps = append(m.SubMaps, block)
for y := 0; y < block.Height; y++ {
for x := 0; x < block.Width; x++ {
m.Grid[y+block.YPos][x+block.XPos]++
}
}
return nil
}

func (m *ShikakuMap) Size() int {
return m.Width * m.Height
}

func (m *ShikakuMap) Blocks() []*ShikakuMap {
blocks := []*ShikakuMap{}

if len(m.SubMaps) > 0 {
for _, subMap := range m.SubMaps {
blocks = append(blocks, subMap.Blocks()...)
}
} else {
blocks = append(blocks, m)
}
return blocks
}

func (m *ShikakuMap) BlockDetailString() string {
return fmt.Sprintf("%d %d-%d %d-%d", m.Size(), m.XPos, m.Width, m.YPos, m.Height)
}

func (m *ShikakuMap) BlockString() string {
return fmt.Sprintf("%d %d %d", m.Size(), m.XPos+m.RandXPos, m.YPos+m.RandYPos)
}

func (m *ShikakuMap) String() string {
lines := []string{
fmt.Sprintf("T %d %d", m.Width, m.Height),
}

for _, block := range m.Blocks() {
lines = append(lines, block.BlockString())
}

return strings.Join(lines, "\n")
}

func (m *ShikakuMap) Draw() string {
output := ""
for _, line := range m.Grid {
for _, col := range line {
switch col {
case 0:
output += "."
case 1:
output += "#"
default:
panic("should not happen")
}
}
output += "\n"
}
return output[:len(output)-1]
}

0 comments on commit 0229279

Please sign in to comment.