laneya2

cave exploration game
git clone https://git.ce9e.org/laneya2.git

commit
0574812fb1feeffe28f527332833eb2eea539a48
parent
752095b992d18b0e77e9d3ed9fa4aa1978c26676
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2024-09-21 08:00
split go code into separate files

Diffstat

M .gitignore 2 +-
M Makefile 10 +++++-----
R laneya.go -> game.go 229 +++++++++----------------------------------------------------
A geo.go 48 ++++++++++++++++++++++++++++++++++++++++++++++++
A server.go 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

5 files changed, 219 insertions, 204 deletions


diff --git a/.gitignore b/.gitignore

@@ -1 +1 @@
    1    -1 laneya
   -1     1 server

diff --git a/Makefile b/Makefile

@@ -1,13 +1,13 @@
    1     1 .PHONY: live
    2     2 live:
    3    -1 	echo laneya.go | entr -r make lint run
   -1     3 	find . -name '*.go' | entr -r make lint run
    4     4 
    5     5 .PHONY: run
    6    -1 run: laneya
    7    -1 	./laneya -v -s
   -1     6 run: server
   -1     7 	./server -v -s
    8     8 
    9    -1 laneya: laneya.go
   10    -1 	go build $<
   -1     9 server: *.go
   -1    10 	go build
   11    11 
   12    12 .PHONY: lint
   13    13 lint:

diff --git a/laneya.go b/game.go

@@ -1,38 +1,12 @@
    1     1 package main
    2     2 
    3     3 import (
    4    -1 	"context"
    5    -1 	"flag"
    6    -1 	"fmt"
    7     4 	"log"
    8    -1 	"math/rand"
    9    -1 	"net"
   10    -1 	"net/http"
   11    -1 	"os"
   12    -1 	"os/signal"
   13     5 	"sync"
   14    -1 	"syscall"
   15    -1 	"time"
   16     6 
   17     7 	"github.com/gorilla/websocket"
   18     8 )
   19     9 
   20    -1 var upgrader = websocket.Upgrader{}
   21    -1 var verbose = false
   22    -1 var static = false
   23    -1 
   24    -1 type Point struct {
   25    -1 	X int `json:"x"`
   26    -1 	Y int `json:"y"`
   27    -1 }
   28    -1 
   29    -1 type Rect struct {
   30    -1 	X1 int `json:"x1"`
   31    -1 	Y1 int `json:"y1"`
   32    -1 	X2 int `json:"x2"`
   33    -1 	Y2 int `json:"y2"`
   34    -1 }
   35    -1 
   36    10 type Message map[string]interface{}
   37    11 
   38    12 type Player struct {
@@ -60,55 +34,12 @@ type Game struct {
   60    34 	Ladder     Point
   61    35 }
   62    36 
   -1    37 var verbose = false
   -1    38 var static = false
   -1    39 
   63    40 var mux = &sync.RWMutex{}
   64    41 var games = make(map[string]*Game)
   65    42 
   66    -1 func makeRect(x1 int, y1 int, x2 int, y2 int) Rect {
   67    -1 	if x1 > x2 {
   68    -1 		x1, x2 = x2, x1
   69    -1 	}
   70    -1 	if y1 > y2 {
   71    -1 		y1, y2 = y2, y1
   72    -1 	}
   73    -1 	return Rect{x1, y1, x2, y2}
   74    -1 }
   75    -1 
   76    -1 func randomRect(n int) Rect {
   77    -1 	x1 := rand.Intn(2*n) - n
   78    -1 	x2 := rand.Intn(2*n) - n
   79    -1 	y1 := rand.Intn(2*n) - n
   80    -1 	y2 := rand.Intn(2*n) - n
   81    -1 	return makeRect(x1, y1, x2, y2)
   82    -1 }
   83    -1 
   84    -1 func (game *Game) generateMap() {
   85    -1 	prev := Rect{-5, -5, 5, 5}
   86    -1 
   87    -1 	game.Rects = []Rect{prev}
   88    -1 	lines := []Rect{}
   89    -1 
   90    -1 	for i := 1; i <= 12; i++ {
   91    -1 		rect := randomRect(50)
   92    -1 		if rect.Area() < 250 {
   93    -1 			game.Rects = append(game.Rects, rect)
   94    -1 
   95    -1 			p1 := prev.Center()
   96    -1 			p2 := rect.Center()
   97    -1 
   98    -1 			lines = append(lines, makeRect(p1.X, p1.Y, p2.X, p1.Y))
   99    -1 			lines = append(lines, makeRect(p2.X, p1.Y, p2.X, p2.Y))
  100    -1 
  101    -1 			prev = rect
  102    -1 		}
  103    -1 	}
  104    -1 
  105    -1 	game.Ladder = prev.Center()
  106    -1 
  107    -1 	for _, line := range lines {
  108    -1 		game.Rects = append(game.Rects, line)
  109    -1 	}
  110    -1 }
  111    -1 
  112    43 func getGame(id string) *Game {
  113    44 	mux.RLock()
  114    45 	game, ok := games[id]
@@ -134,21 +65,6 @@ func getGame(id string) *Game {
  134    65 	return game
  135    66 }
  136    67 
  137    -1 func (rect *Rect) Contains(x int, y int) bool {
  138    -1 	return x >= rect.X1 && x <= rect.X2 && y >= rect.Y1 && y <= rect.Y2
  139    -1 }
  140    -1 
  141    -1 func (rect *Rect) Area() int {
  142    -1 	return (rect.X2 - rect.X1) * (rect.Y2 - rect.Y1)
  143    -1 }
  144    -1 
  145    -1 func (rect *Rect) Center() Point {
  146    -1 	return Point{
  147    -1 		(rect.X2 + rect.X1) / 2,
  148    -1 		(rect.Y2 + rect.Y1) / 2,
  149    -1 	}
  150    -1 }
  151    -1 
  152    68 func (game *Game) broadcast(msgs []Message) {
  153    69 	for player, _ := range game.Players {
  154    70 		player.Send <- msgs
@@ -160,6 +76,34 @@ func (game *Game) createId() int {
  160    76 	return game.lastId
  161    77 }
  162    78 
   -1    79 func (game *Game) generateMap() {
   -1    80 	prev := Rect{-5, -5, 5, 5}
   -1    81 
   -1    82 	game.Rects = []Rect{prev}
   -1    83 	lines := []Rect{}
   -1    84 
   -1    85 	for i := 1; i <= 12; i++ {
   -1    86 		rect := randomRect(50)
   -1    87 		if rect.Area() < 250 {
   -1    88 			game.Rects = append(game.Rects, rect)
   -1    89 
   -1    90 			p1 := prev.Center()
   -1    91 			p2 := rect.Center()
   -1    92 
   -1    93 			lines = append(lines, makeRect(p1.X, p1.Y, p2.X, p1.Y))
   -1    94 			lines = append(lines, makeRect(p2.X, p1.Y, p2.X, p2.Y))
   -1    95 
   -1    96 			prev = rect
   -1    97 		}
   -1    98 	}
   -1    99 
   -1   100 	game.Ladder = prev.Center()
   -1   101 
   -1   102 	for _, line := range lines {
   -1   103 		game.Rects = append(game.Rects, line)
   -1   104 	}
   -1   105 }
   -1   106 
  163   107 func (game *Game) IsFree(x int, y int) bool {
  164   108 	for _, rect := range game.Rects {
  165   109 		if rect.Contains(x, y) {
@@ -279,114 +223,3 @@ func (game *Game) run() {
  279   223 		}
  280   224 	}
  281   225 }
  282    -1 
  283    -1 func (player *Player) readPump() {
  284    -1 	defer func() {
  285    -1 		player.Game.unregister <- player
  286    -1 		player.conn.Close()
  287    -1 	}()
  288    -1 
  289    -1 	for {
  290    -1 		msg := Message{}
  291    -1 		err := player.conn.ReadJSON(&msg)
  292    -1 		if err != nil {
  293    -1 			return
  294    -1 		}
  295    -1 		player.Game.Msg <- PlayerMessage{player, msg}
  296    -1 	}
  297    -1 }
  298    -1 
  299    -1 func (player *Player) writePump() {
  300    -1 	defer player.conn.Close()
  301    -1 	ticker := time.NewTicker(60 * time.Second)
  302    -1 	defer ticker.Stop()
  303    -1 
  304    -1 	for {
  305    -1 		select {
  306    -1 		case data := <-player.Send:
  307    -1 			err := player.conn.WriteJSON(data)
  308    -1 			if err != nil {
  309    -1 				return
  310    -1 			}
  311    -1 		case <-ticker.C:
  312    -1 			if !player.alive {
  313    -1 				return
  314    -1 			}
  315    -1 			player.alive = false
  316    -1 			err := player.conn.WriteMessage(websocket.PingMessage, nil)
  317    -1 			if err != nil {
  318    -1 				return
  319    -1 			}
  320    -1 		}
  321    -1 	}
  322    -1 }
  323    -1 
  324    -1 func serveWs(w http.ResponseWriter, r *http.Request) {
  325    -1 	conn, err := upgrader.Upgrade(w, r, nil)
  326    -1 	if err != nil {
  327    -1 		log.Println(err)
  328    -1 		return
  329    -1 	}
  330    -1 
  331    -1 	game := getGame(r.PathValue("id"))
  332    -1 
  333    -1 	player := &Player{
  334    -1 		Game:  game,
  335    -1 		Send:  make(chan []Message),
  336    -1 		conn:  conn,
  337    -1 		alive: true,
  338    -1 		Id:    game.createId(),
  339    -1 		Pos:   Point{0, 0},
  340    -1 	}
  341    -1 	conn.SetPongHandler(func(string) error {
  342    -1 		player.alive = true
  343    -1 		return nil
  344    -1 	})
  345    -1 	game.register <- player
  346    -1 
  347    -1 	go player.writePump()
  348    -1 	go player.readPump()
  349    -1 }
  350    -1 
  351    -1 func main() {
  352    -1 	flag.Usage = func() {
  353    -1 		fmt.Fprintf(os.Stderr, "laneya [-v] [-s] [port]\n")
  354    -1 		flag.PrintDefaults()
  355    -1 	}
  356    -1 
  357    -1 	flag.BoolVar(&verbose, "v", false, "enable verbose logs")
  358    -1 	flag.BoolVar(&static, "s", false, "serve static files (for development)")
  359    -1 	flag.Parse()
  360    -1 
  361    -1 	addr := "localhost:8000"
  362    -1 	if len(flag.Args()) > 0 {
  363    -1 		addr = fmt.Sprintf("localhost:%s", flag.Args()[0])
  364    -1 	}
  365    -1 
  366    -1 	if static {
  367    -1 		http.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
  368    -1 			http.ServeFile(w, r, "index.html")
  369    -1 		})
  370    -1 		http.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
  371    -1 	}
  372    -1 	http.HandleFunc("GET /ws/{id}", serveWs)
  373    -1 
  374    -1 	ctx, unregisterSignals := signal.NotifyContext(
  375    -1 		context.Background(), os.Interrupt, syscall.SIGTERM,
  376    -1 	)
  377    -1 	ctxFactory := func(l net.Listener) context.Context { return ctx }
  378    -1 	server := &http.Server{Addr: addr, BaseContext: ctxFactory}
  379    -1 
  380    -1 	go func() {
  381    -1 		log.Printf("Serving on http://%s", addr)
  382    -1 		err := server.ListenAndServe()
  383    -1 		if err != http.ErrServerClosed {
  384    -1 			log.Fatal(err)
  385    -1 		}
  386    -1 	}()
  387    -1 
  388    -1 	<-ctx.Done()
  389    -1 	unregisterSignals()
  390    -1 	log.Println("Shutting down server…")
  391    -1 	server.Shutdown(context.Background())
  392    -1 }

diff --git a/geo.go b/geo.go

@@ -0,0 +1,48 @@
   -1     1 package main
   -1     2 
   -1     3 import "math/rand"
   -1     4 
   -1     5 type Point struct {
   -1     6 	X int `json:"x"`
   -1     7 	Y int `json:"y"`
   -1     8 }
   -1     9 
   -1    10 type Rect struct {
   -1    11 	X1 int `json:"x1"`
   -1    12 	Y1 int `json:"y1"`
   -1    13 	X2 int `json:"x2"`
   -1    14 	Y2 int `json:"y2"`
   -1    15 }
   -1    16 
   -1    17 func makeRect(x1 int, y1 int, x2 int, y2 int) Rect {
   -1    18 	if x1 > x2 {
   -1    19 		x1, x2 = x2, x1
   -1    20 	}
   -1    21 	if y1 > y2 {
   -1    22 		y1, y2 = y2, y1
   -1    23 	}
   -1    24 	return Rect{x1, y1, x2, y2}
   -1    25 }
   -1    26 
   -1    27 func randomRect(n int) Rect {
   -1    28 	x1 := rand.Intn(2*n) - n
   -1    29 	x2 := rand.Intn(2*n) - n
   -1    30 	y1 := rand.Intn(2*n) - n
   -1    31 	y2 := rand.Intn(2*n) - n
   -1    32 	return makeRect(x1, y1, x2, y2)
   -1    33 }
   -1    34 
   -1    35 func (rect *Rect) Area() int {
   -1    36 	return (rect.X2 - rect.X1) * (rect.Y2 - rect.Y1)
   -1    37 }
   -1    38 
   -1    39 func (rect *Rect) Contains(x int, y int) bool {
   -1    40 	return x >= rect.X1 && x <= rect.X2 && y >= rect.Y1 && y <= rect.Y2
   -1    41 }
   -1    42 
   -1    43 func (rect *Rect) Center() Point {
   -1    44 	return Point{
   -1    45 		(rect.X2 + rect.X1) / 2,
   -1    46 		(rect.Y2 + rect.Y1) / 2,
   -1    47 	}
   -1    48 }

diff --git a/server.go b/server.go

@@ -0,0 +1,134 @@
   -1     1 package main
   -1     2 
   -1     3 import (
   -1     4 	"context"
   -1     5 	"flag"
   -1     6 	"fmt"
   -1     7 	"log"
   -1     8 	"net"
   -1     9 	"net/http"
   -1    10 	"os"
   -1    11 	"os/signal"
   -1    12 	"syscall"
   -1    13 	"time"
   -1    14 
   -1    15 	"github.com/gorilla/websocket"
   -1    16 )
   -1    17 
   -1    18 var upgrader = websocket.Upgrader{}
   -1    19 
   -1    20 func (player *Player) readPump() {
   -1    21 	defer func() {
   -1    22 		player.Game.unregister <- player
   -1    23 		player.conn.Close()
   -1    24 	}()
   -1    25 
   -1    26 	for {
   -1    27 		msg := Message{}
   -1    28 		err := player.conn.ReadJSON(&msg)
   -1    29 		if err != nil {
   -1    30 			return
   -1    31 		}
   -1    32 		player.Game.Msg <- PlayerMessage{player, msg}
   -1    33 	}
   -1    34 }
   -1    35 
   -1    36 func (player *Player) writePump() {
   -1    37 	defer player.conn.Close()
   -1    38 	ticker := time.NewTicker(60 * time.Second)
   -1    39 	defer ticker.Stop()
   -1    40 
   -1    41 	for {
   -1    42 		select {
   -1    43 		case data := <-player.Send:
   -1    44 			err := player.conn.WriteJSON(data)
   -1    45 			if err != nil {
   -1    46 				return
   -1    47 			}
   -1    48 		case <-ticker.C:
   -1    49 			if !player.alive {
   -1    50 				return
   -1    51 			}
   -1    52 			player.alive = false
   -1    53 			err := player.conn.WriteMessage(websocket.PingMessage, nil)
   -1    54 			if err != nil {
   -1    55 				return
   -1    56 			}
   -1    57 		}
   -1    58 	}
   -1    59 }
   -1    60 
   -1    61 func serveWs(w http.ResponseWriter, r *http.Request) {
   -1    62 	conn, err := upgrader.Upgrade(w, r, nil)
   -1    63 	if err != nil {
   -1    64 		log.Println(err)
   -1    65 		return
   -1    66 	}
   -1    67 
   -1    68 	game := getGame(r.PathValue("id"))
   -1    69 
   -1    70 	player := &Player{
   -1    71 		Game:  game,
   -1    72 		Send:  make(chan []Message),
   -1    73 		conn:  conn,
   -1    74 		alive: true,
   -1    75 		Id:    game.createId(),
   -1    76 		Pos:   Point{0, 0},
   -1    77 	}
   -1    78 	conn.SetPongHandler(func(string) error {
   -1    79 		player.alive = true
   -1    80 		return nil
   -1    81 	})
   -1    82 	game.register <- player
   -1    83 
   -1    84 	go player.writePump()
   -1    85 	go player.readPump()
   -1    86 }
   -1    87 
   -1    88 func serve(addr string) {
   -1    89 	http.HandleFunc("GET /ws/{id}", serveWs)
   -1    90 
   -1    91 	ctx, unregisterSignals := signal.NotifyContext(
   -1    92 		context.Background(), os.Interrupt, syscall.SIGTERM,
   -1    93 	)
   -1    94 	ctxFactory := func(l net.Listener) context.Context { return ctx }
   -1    95 	server := &http.Server{Addr: addr, BaseContext: ctxFactory}
   -1    96 
   -1    97 	go func() {
   -1    98 		log.Printf("Serving on http://%s", addr)
   -1    99 		err := server.ListenAndServe()
   -1   100 		if err != http.ErrServerClosed {
   -1   101 			log.Fatal(err)
   -1   102 		}
   -1   103 	}()
   -1   104 
   -1   105 	<-ctx.Done()
   -1   106 	unregisterSignals()
   -1   107 	log.Println("Shutting down server…")
   -1   108 	server.Shutdown(context.Background())
   -1   109 }
   -1   110 
   -1   111 func main() {
   -1   112 	flag.Usage = func() {
   -1   113 		fmt.Fprintf(os.Stderr, "laneya [-v] [-s] [port]\n")
   -1   114 		flag.PrintDefaults()
   -1   115 	}
   -1   116 
   -1   117 	flag.BoolVar(&verbose, "v", false, "enable verbose logs")
   -1   118 	flag.BoolVar(&static, "s", false, "serve static files (for development)")
   -1   119 	flag.Parse()
   -1   120 
   -1   121 	addr := "localhost:8000"
   -1   122 	if len(flag.Args()) > 0 {
   -1   123 		addr = fmt.Sprintf("localhost:%s", flag.Args()[0])
   -1   124 	}
   -1   125 
   -1   126 	if static {
   -1   127 		http.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
   -1   128 			http.ServeFile(w, r, "index.html")
   -1   129 		})
   -1   130 		http.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
   -1   131 	}
   -1   132 
   -1   133 	serve(addr)
   -1   134 }