Initial commit
This commit is contained in:
commit
f26c478727
18 changed files with 621 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/game
|
||||
5
go.mod
Normal file
5
go.mod
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
module github.com/pnx/go-raytracer
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require github.com/veandco/go-sdl2 v0.4.40
|
||||
2
go.sum
Normal file
2
go.sum
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
github.com/veandco/go-sdl2 v0.4.40 h1:fZv6wC3zz1Xt167P09gazawnpa0KY5LM7JAvKpX9d/U=
|
||||
github.com/veandco/go-sdl2 v0.4.40/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
|
||||
65
graphics/sdl.go
Normal file
65
graphics/sdl.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package graphics
|
||||
|
||||
import (
|
||||
"github.com/pnx/go-raytracer/math"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
w, h int32
|
||||
window *sdl.Window
|
||||
renderer *sdl.Renderer
|
||||
}
|
||||
|
||||
func (c Context) Width() int32 {
|
||||
return c.w
|
||||
}
|
||||
|
||||
func (c Context) Height() int32 {
|
||||
return c.h
|
||||
}
|
||||
|
||||
func (c Context) DrawLine(x1 int32, y1 int32, x2 int32, y2 int32, color math.Color) {
|
||||
c.renderer.SetDrawColor(color.R, color.G, color.B, color.A)
|
||||
c.renderer.DrawLine(x1, y1, x2, y2)
|
||||
}
|
||||
|
||||
func (c Context) DrawRect(x, y, w, h int32, color math.Color) {
|
||||
c.renderer.SetDrawColor(color.R, color.G, color.B, color.A)
|
||||
c.renderer.FillRect(&sdl.Rect{
|
||||
X: x,
|
||||
Y: y,
|
||||
W: w,
|
||||
H: h,
|
||||
})
|
||||
}
|
||||
|
||||
func (c Context) Sync() {
|
||||
c.renderer.Present()
|
||||
}
|
||||
|
||||
func (c Context) Exit() {
|
||||
c.window.Destroy()
|
||||
c.renderer.Destroy()
|
||||
sdl.Quit()
|
||||
}
|
||||
|
||||
func Init(title string, w, h int32) (*Context, error) {
|
||||
if err := sdl.Init(sdl.INIT_VIDEO); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
window, err := sdl.CreateWindow(title, sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, w, h, sdl.WINDOW_SHOWN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
renderer, err := sdl.CreateRenderer(window, -1, sdl.RENDERER_SOFTWARE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Context{
|
||||
w: w,
|
||||
h: h,
|
||||
window: window,
|
||||
renderer: renderer,
|
||||
}, nil
|
||||
}
|
||||
97
main.go
Normal file
97
main.go
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
stdmath "math"
|
||||
"time"
|
||||
|
||||
"github.com/pnx/go-raytracer/graphics"
|
||||
"github.com/pnx/go-raytracer/math"
|
||||
"github.com/pnx/go-raytracer/render"
|
||||
"github.com/pnx/go-raytracer/world"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
var (
|
||||
gfxContext *graphics.Context
|
||||
player Player
|
||||
level *world.Level
|
||||
startOfFrame time.Time
|
||||
)
|
||||
|
||||
func loadLevel(newLevel *world.Level) {
|
||||
level = newLevel
|
||||
|
||||
// Set player start position
|
||||
start := level.PlayerStart()
|
||||
|
||||
player.Position = math.Position{
|
||||
X: (float64(start.X) + 0.5) * tileSize,
|
||||
Y: (float64(start.Y) + 0.5) * tileSize,
|
||||
}
|
||||
}
|
||||
|
||||
func update() bool {
|
||||
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
|
||||
switch ev := event.(type) {
|
||||
case *sdl.QuitEvent:
|
||||
return true
|
||||
case *sdl.KeyboardEvent:
|
||||
if ev.Type == sdl.KEYDOWN {
|
||||
switch ev.Keysym.Sym {
|
||||
case sdl.K_ESCAPE:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keys := sdl.GetKeyboardState()
|
||||
|
||||
if keys[sdl.SCANCODE_UP] != 0 {
|
||||
player.MoveForward(level)
|
||||
}
|
||||
if keys[sdl.SCANCODE_DOWN] != 0 {
|
||||
player.MoveBackward(level)
|
||||
}
|
||||
if keys[sdl.SCANCODE_LEFT] != 0 {
|
||||
player.RotateLeft()
|
||||
}
|
||||
if keys[sdl.SCANCODE_RIGHT] != 0 {
|
||||
player.RotateRight()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
gfxContext, err = graphics.Init("Go Raycaster", 1024, 768)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
player = Player{
|
||||
MoveSpd: 2,
|
||||
RotSpd: 0.05,
|
||||
}
|
||||
|
||||
loadLevel(&world.Level1)
|
||||
|
||||
for {
|
||||
startOfFrame := time.Now()
|
||||
if update() {
|
||||
break
|
||||
}
|
||||
|
||||
render.DrawScene(gfxContext, player.Transform, level, colorMap)
|
||||
render.DrawMiniMap(gfxContext, player.Transform, level)
|
||||
gfxContext.Sync()
|
||||
|
||||
elapsed := time.Since(startOfFrame)
|
||||
sdl.Delay(uint32(stdmath.Max(16-float64(elapsed.Milliseconds()), 1)))
|
||||
}
|
||||
|
||||
gfxContext.Exit()
|
||||
}
|
||||
6
makefile
Normal file
6
makefile
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
GO=go
|
||||
|
||||
.PHONY: game
|
||||
|
||||
game:
|
||||
$(GO) build -o $@ .
|
||||
23
math/color.go
Normal file
23
math/color.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package math
|
||||
|
||||
type Color struct {
|
||||
R, G, B, A uint8
|
||||
}
|
||||
|
||||
func (c Color) Shade(value uint8) Color {
|
||||
return Color{
|
||||
R: c.R / value,
|
||||
G: c.G / value,
|
||||
B: c.B / value,
|
||||
A: c.A,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Color) Sub(value uint8) Color {
|
||||
return Color{
|
||||
R: c.R - value,
|
||||
G: c.G - value,
|
||||
B: c.B - value,
|
||||
A: c.A,
|
||||
}
|
||||
}
|
||||
7
math/degree.go
Normal file
7
math/degree.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package math
|
||||
|
||||
import "math"
|
||||
|
||||
func DecToRad(deg float64) float64 {
|
||||
return deg * (math.Pi / 180)
|
||||
}
|
||||
26
math/direction.go
Normal file
26
math/direction.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package math
|
||||
|
||||
import (
|
||||
stdmath "math"
|
||||
)
|
||||
|
||||
type Direction float64
|
||||
|
||||
func (d *Direction) Set(v float64) {
|
||||
*d = Direction(v)
|
||||
}
|
||||
|
||||
func (d Direction) Get() float64 {
|
||||
return float64(d)
|
||||
}
|
||||
|
||||
func (d Direction) ForwardVector() Vec2f {
|
||||
return Vec2f{
|
||||
X: stdmath.Cos(float64(d)),
|
||||
Y: stdmath.Sin(float64(d)),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Direction) Rotate(delta float64) {
|
||||
*d += Direction(delta)
|
||||
}
|
||||
11
math/position.go
Normal file
11
math/position.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package math
|
||||
|
||||
type Position Vec2f
|
||||
|
||||
func (p *Position) Set(v Vec2f) {
|
||||
*p = Position(v)
|
||||
}
|
||||
|
||||
func (p Position) Get() Vec2f {
|
||||
return Vec2f(p)
|
||||
}
|
||||
6
math/transform.go
Normal file
6
math/transform.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package math
|
||||
|
||||
type Transform struct {
|
||||
Position
|
||||
Direction
|
||||
}
|
||||
82
math/vec2.go
Normal file
82
math/vec2.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package math
|
||||
|
||||
import stdmath "math"
|
||||
|
||||
type UnsignedInteger interface {
|
||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
|
||||
}
|
||||
|
||||
type SignedInteger interface {
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64
|
||||
}
|
||||
|
||||
type Integer interface {
|
||||
UnsignedInteger | SignedInteger
|
||||
}
|
||||
|
||||
type Float interface {
|
||||
~float32 | ~float64
|
||||
}
|
||||
|
||||
type Number interface {
|
||||
Integer | Float
|
||||
}
|
||||
|
||||
type Vec2[T Number] struct {
|
||||
X, Y T
|
||||
}
|
||||
|
||||
type (
|
||||
Vec2i = Vec2[int]
|
||||
Vec2i32 = Vec2[int32]
|
||||
Vec2f = Vec2[float64]
|
||||
Vec2u8 = Vec2[uint8]
|
||||
)
|
||||
|
||||
func (v Vec2[T]) Add(x, y T) Vec2[T] {
|
||||
return Vec2[T]{
|
||||
X: v.X + x,
|
||||
Y: v.Y + y,
|
||||
}
|
||||
}
|
||||
|
||||
func (v Vec2[T]) AddS(s T) Vec2[T] {
|
||||
return v.Add(s, s)
|
||||
}
|
||||
|
||||
func (v Vec2[T]) AddVec(other Vec2[T]) Vec2[T] {
|
||||
return Vec2[T]{
|
||||
X: v.X + other.X,
|
||||
Y: v.Y + other.Y,
|
||||
}
|
||||
}
|
||||
|
||||
func (v Vec2[T]) Mul(x, y T) Vec2[T] {
|
||||
return Vec2[T]{
|
||||
X: v.X * x,
|
||||
Y: v.Y * y,
|
||||
}
|
||||
}
|
||||
|
||||
func (v Vec2[T]) MulVec(other Vec2[T]) Vec2[T] {
|
||||
return v.Mul(other.X, other.Y)
|
||||
}
|
||||
|
||||
func (v Vec2[T]) Scale(f T) Vec2[T] {
|
||||
return v.Mul(f, f)
|
||||
}
|
||||
|
||||
func (v Vec2[T]) Length() float64 {
|
||||
return stdmath.Sqrt(float64(v.X*v.X) + float64(v.Y*v.Y))
|
||||
}
|
||||
|
||||
func (v Vec2[T]) Normalize() Vec2[T] {
|
||||
l := v.Length()
|
||||
if l <= 0.0 {
|
||||
return Vec2[T]{}
|
||||
}
|
||||
return Vec2[T]{
|
||||
X: T(float64(v.X) / l),
|
||||
Y: T(float64(v.Y) / l),
|
||||
}
|
||||
}
|
||||
38
player.go
Normal file
38
player.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/pnx/go-raytracer/math"
|
||||
"github.com/pnx/go-raytracer/world"
|
||||
)
|
||||
|
||||
type Player struct {
|
||||
math.Transform
|
||||
MoveSpd float64
|
||||
RotSpd float64
|
||||
}
|
||||
|
||||
func (p *Player) Move(delta float64, level *world.Level) {
|
||||
deltaVec := p.ForwardVector().Scale(delta)
|
||||
newPos := p.Position.Get().AddVec(deltaVec)
|
||||
|
||||
// make sure we don't move past walls.
|
||||
if !level.Wall(int(newPos.X)/world.TileSize, int(newPos.Y)/world.TileSize) {
|
||||
p.Position.Set(newPos)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Player) MoveForward(level *world.Level) {
|
||||
p.Move(p.MoveSpd, level)
|
||||
}
|
||||
|
||||
func (p *Player) MoveBackward(level *world.Level) {
|
||||
p.Move(-p.MoveSpd, level)
|
||||
}
|
||||
|
||||
func (p *Player) RotateLeft() {
|
||||
p.Rotate(-p.RotSpd)
|
||||
}
|
||||
|
||||
func (p *Player) RotateRight() {
|
||||
p.Rotate(p.RotSpd)
|
||||
}
|
||||
47
render/engine.go
Normal file
47
render/engine.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package render
|
||||
|
||||
import (
|
||||
"github.com/pnx/go-raytracer/graphics"
|
||||
"github.com/pnx/go-raytracer/math"
|
||||
"github.com/pnx/go-raytracer/world"
|
||||
)
|
||||
|
||||
func DrawColumn(ctx *graphics.Context, x int32, wall_h int32, color math.Color) {
|
||||
if wall_h < ctx.Height() {
|
||||
y1 := (ctx.Height() - wall_h) / 2
|
||||
y2 := (ctx.Height() + wall_h) / 2
|
||||
|
||||
// Top
|
||||
// renderer.SetDrawColor(90, 90, 0, 255)
|
||||
ctx.DrawLine(x, 0, int32(x), y1, math.Color{R: 90, G: 90, B: 0, A: 255})
|
||||
|
||||
// // Middle
|
||||
// renderer.SetDrawColor(color.R, color.G, color.B, color.A)
|
||||
ctx.DrawLine(x, y1, int32(x), y2, color)
|
||||
|
||||
// Bottom
|
||||
if y2 < ctx.Height() {
|
||||
// renderer.SetDrawColor(50, 50, 50, 255)
|
||||
ctx.DrawLine(x, y2, int32(x), ctx.Height(), math.Color{R: 50, G: 50, B: 50, A: 255})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ctx.SetDrawColor(color.R, color.G, color.B, color.A)
|
||||
ctx.DrawLine(x, 0, int32(x), ctx.Height(), color)
|
||||
}
|
||||
|
||||
func DrawScene(ctx *graphics.Context, camera math.Transform, level *world.Level, colorMap []math.Color) {
|
||||
for x := range ctx.Width() {
|
||||
result := CastRay(camera, level, int(x), int(ctx.Width()))
|
||||
lineHeight := int(float64(ctx.Height()) / result.Distance)
|
||||
|
||||
color := colorMap[level.Cell(int(result.Cell.X), int(result.Cell.Y))-1]
|
||||
|
||||
if result.Side > 0 {
|
||||
color = color.Shade(2)
|
||||
}
|
||||
|
||||
DrawColumn(ctx, int32(x), int32(lineHeight), color)
|
||||
}
|
||||
}
|
||||
36
render/minimap.go
Normal file
36
render/minimap.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package render
|
||||
|
||||
import (
|
||||
"github.com/pnx/go-raytracer/graphics"
|
||||
"github.com/pnx/go-raytracer/math"
|
||||
"github.com/pnx/go-raytracer/world"
|
||||
)
|
||||
|
||||
func DrawMiniMap(ctx *graphics.Context, camera math.Transform, level *world.Level) {
|
||||
const tileSize = 32
|
||||
const scale = float64(tileSize) / float64(world.TileSize)
|
||||
// offset := math.Vec2i32{X: 25, Y: 25}
|
||||
// size := math.Vec2i32{X: 200, Y: 100}
|
||||
|
||||
ctx.DrawRect(0, 0, int32(level.W*tileSize)+3, int32(level.H*tileSize)+3, math.Color{})
|
||||
|
||||
for y := range level.H {
|
||||
for x := range level.W {
|
||||
if level.Wall(x, y) {
|
||||
ctx.DrawRect(int32(x*tileSize), int32(y*tileSize), tileSize, tileSize, math.Color{R: 255, G: 255, B: 255, A: 255})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Camera Position
|
||||
cPos := camera.Position.Get().Scale(scale)
|
||||
ctx.DrawRect(int32(cPos.X-2), int32(cPos.Y-2), 4, 4, math.Color{R: 255, G: 0, B: 255, A: 255})
|
||||
|
||||
// Rays
|
||||
for x := range ctx.Width() {
|
||||
hit := CastRay(camera, level, int(x), int(ctx.Width()))
|
||||
end := camera.Position.Get().AddVec(hit.Pos.Scale(world.TileSize)).Scale(scale)
|
||||
|
||||
ctx.DrawLine(int32(cPos.X), int32(cPos.Y), int32(end.X), int32(end.Y), math.Color{R: 150, G: 150, B: 0, A: 255})
|
||||
}
|
||||
}
|
||||
85
render/raycaster.go
Normal file
85
render/raycaster.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package render
|
||||
|
||||
import (
|
||||
stdmath "math"
|
||||
|
||||
"github.com/pnx/go-raytracer/math"
|
||||
"github.com/pnx/go-raytracer/world"
|
||||
)
|
||||
|
||||
type RayHit struct {
|
||||
Cell math.Vec2u8
|
||||
Side int
|
||||
Distance float64
|
||||
Pos math.Vec2f
|
||||
}
|
||||
|
||||
func CastRay(camera math.Transform, level *world.Level, x int, max_rays int) RayHit {
|
||||
angle := camera.Direction.Get()
|
||||
cameraX := 2*float64(x)/float64(max_rays) - 1
|
||||
rayDirX := stdmath.Cos(angle) + cameraX*stdmath.Cos(angle+stdmath.Pi/2)
|
||||
rayDirY := stdmath.Sin(angle) + cameraX*stdmath.Sin(angle+stdmath.Pi/2)
|
||||
|
||||
mapX := int(camera.X) / world.TileSize
|
||||
mapY := int(camera.Y) / world.TileSize
|
||||
|
||||
deltaDistX := stdmath.Abs(1 / rayDirX)
|
||||
deltaDistY := stdmath.Abs(1 / rayDirY)
|
||||
|
||||
var stepX, stepY int
|
||||
var sideDistX, sideDistY float64
|
||||
|
||||
if rayDirX < 0 {
|
||||
stepX = -1
|
||||
sideDistX = (float64(camera.X)/world.TileSize - float64(mapX)) * deltaDistX
|
||||
} else {
|
||||
stepX = 1
|
||||
sideDistX = (float64(mapX+1) - float64(camera.X)/world.TileSize) * deltaDistX
|
||||
}
|
||||
if rayDirY < 0 {
|
||||
stepY = -1
|
||||
sideDistY = (float64(camera.Y)/world.TileSize - float64(mapY)) * deltaDistY
|
||||
} else {
|
||||
stepY = 1
|
||||
sideDistY = (float64(mapY+1) - float64(camera.Y)/world.TileSize) * deltaDistY
|
||||
}
|
||||
|
||||
// DDA loop
|
||||
var side int // 0 = x, 1 = y
|
||||
for {
|
||||
if sideDistX < sideDistY {
|
||||
sideDistX += deltaDistX
|
||||
mapX += stepX
|
||||
side = 0
|
||||
} else {
|
||||
sideDistY += deltaDistY
|
||||
mapY += stepY
|
||||
side = 1
|
||||
}
|
||||
|
||||
if mapX < 0 || mapX >= level.W || mapY < 0 || mapY >= level.H {
|
||||
break // out of bounds
|
||||
}
|
||||
if level.Wall(mapX, mapY) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate distance to wall
|
||||
var perpWallDist float64
|
||||
if side == 0 {
|
||||
perpWallDist = (float64(mapX) - float64(camera.X)/world.TileSize + float64(1-stepX)/2) / rayDirX
|
||||
} else {
|
||||
perpWallDist = (float64(mapY) - float64(camera.Y)/world.TileSize + float64(1-stepY)/2) / rayDirY
|
||||
}
|
||||
|
||||
return RayHit{
|
||||
Cell: math.Vec2u8{X: uint8(mapX), Y: uint8(mapY)},
|
||||
Side: side,
|
||||
Distance: perpWallDist,
|
||||
Pos: math.Vec2f{
|
||||
X: rayDirX * perpWallDist,
|
||||
Y: rayDirY * perpWallDist,
|
||||
},
|
||||
}
|
||||
}
|
||||
42
world/level.go
Normal file
42
world/level.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package world
|
||||
|
||||
import "github.com/pnx/go-raytracer/math"
|
||||
|
||||
const TileSize = 64
|
||||
|
||||
type Special byte
|
||||
|
||||
const (
|
||||
Empty Special = 0
|
||||
PlayerStart Special = 'S'
|
||||
)
|
||||
|
||||
type Level struct {
|
||||
W, H int
|
||||
Grid []byte
|
||||
Specials []Special
|
||||
}
|
||||
|
||||
func (m Level) Wall(x, y int) bool {
|
||||
return m.Grid[(y*m.W)+x] > 0
|
||||
}
|
||||
|
||||
func (m Level) Cell(x, y int) byte {
|
||||
return m.Grid[(y*m.W)+x]
|
||||
}
|
||||
|
||||
func (m Level) PosToCell(x, y float64) (int, int) {
|
||||
return int(x) / TileSize, int(y) / TileSize
|
||||
}
|
||||
|
||||
func (m Level) PlayerStart() math.Vec2[int] {
|
||||
for k, v := range m.Specials {
|
||||
if v == PlayerStart {
|
||||
return math.Vec2[int]{
|
||||
X: k % m.W,
|
||||
Y: k / m.W,
|
||||
}
|
||||
}
|
||||
}
|
||||
return math.Vec2[int]{X: -1, Y: -1}
|
||||
}
|
||||
42
world/level1.go
Normal file
42
world/level1.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package world
|
||||
|
||||
var Level1 = Level{
|
||||
W: 16,
|
||||
H: 16,
|
||||
Grid: []uint8{
|
||||
2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 2, 0, 1,
|
||||
2, 2, 2, 0, 2, 2, 2, 2, 0, 4, 4, 0, 0, 0, 0, 1,
|
||||
3, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
|
||||
3, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1,
|
||||
3, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 4, 0, 3, 0, 0, 1,
|
||||
3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 1,
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
},
|
||||
Specials: []Special{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 'S', 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
},
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue