base cli game
This commit is contained in:
commit
41f26363c8
6 changed files with 472 additions and 0 deletions
9
cli/main.go
Normal file
9
cli/main.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"15/lib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
lib.Start()
|
||||||
|
}
|
15
go.mod
Normal file
15
go.mod
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
module 15
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
atomicgo.dev/keyboard v0.2.9
|
||||||
|
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
|
||||||
|
)
|
||||||
|
|
||||||
|
require github.com/vcaesar/keycode v0.10.0 // indirect
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/containerd/console v1.0.3 // indirect
|
||||||
|
golang.org/x/sys v0.10.0 // indirect
|
||||||
|
)
|
58
go.sum
Normal file
58
go.sum
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
|
||||||
|
atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
|
||||||
|
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
|
||||||
|
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
|
||||||
|
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
|
||||||
|
github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
|
||||||
|
github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=
|
||||||
|
github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
|
||||||
|
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
|
||||||
|
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
|
||||||
|
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
|
||||||
|
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
|
||||||
|
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
|
||||||
|
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
|
||||||
|
github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
|
||||||
|
github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
|
||||||
|
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
|
||||||
|
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
|
||||||
|
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/vcaesar/keycode v0.10.0/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ=
|
||||||
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||||
|
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
|
||||||
|
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||||
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
210
lib/board.go
Normal file
210
lib/board.go
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Board struct {
|
||||||
|
grid [16]int
|
||||||
|
empty [2]int
|
||||||
|
}
|
||||||
|
|
||||||
|
const ROW_COUNT = 4
|
||||||
|
|
||||||
|
var SOLVED_GRID = [16]int{
|
||||||
|
1, 2, 3, 4,
|
||||||
|
5, 6, 7, 8,
|
||||||
|
9, 10, 11, 12,
|
||||||
|
13, 14, 15, 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Direction int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LEFT Direction = iota
|
||||||
|
RIGHT
|
||||||
|
UP
|
||||||
|
DOWN
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewBoard() *Board {
|
||||||
|
return &Board{
|
||||||
|
grid: [16]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0},
|
||||||
|
empty: [2]int{3, 3},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all pieces on their desired places
|
||||||
|
// no more moves are needed and we can say that
|
||||||
|
// board is solved.
|
||||||
|
func (board *Board) Solved() bool {
|
||||||
|
return board.neededMoves() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Faster way to check if board is solved.
|
||||||
|
// Arrays are comparable in Go so we can simply
|
||||||
|
// compare desired state with current
|
||||||
|
func (board *Board) SolvedFast() bool {
|
||||||
|
return board.grid == SOLVED_GRID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Board) Print() {
|
||||||
|
for i, cell := range b.grid {
|
||||||
|
if cell == 0 {
|
||||||
|
fmt.Printf(" ")
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%3d", cell)
|
||||||
|
}
|
||||||
|
if i == 3 || i == 7 || i == 11 {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Board) PossibleDirections() []Direction {
|
||||||
|
directions := []Direction{}
|
||||||
|
|
||||||
|
if b.empty[0] != 0 {
|
||||||
|
directions = append(directions, UP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.empty[0] != 3 {
|
||||||
|
directions = append(directions, DOWN)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.empty[1] != 0 {
|
||||||
|
directions = append(directions, LEFT)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.empty[1] != 3 {
|
||||||
|
directions = append(directions, RIGHT)
|
||||||
|
}
|
||||||
|
|
||||||
|
return directions
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Moves" empty cell to new position.
|
||||||
|
// It's easier to reason if we will move
|
||||||
|
// empty cell as another piece rather than
|
||||||
|
// moving pieces that are surrounds it.
|
||||||
|
func (b *Board) Move(d Direction) {
|
||||||
|
possibleDirections := b.PossibleDirections()
|
||||||
|
|
||||||
|
if slices.Index(possibleDirections, d) == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toRow, toCol := directionToStep(d)
|
||||||
|
|
||||||
|
newRow := b.empty[0] + toRow
|
||||||
|
newCol := b.empty[1] + toCol
|
||||||
|
|
||||||
|
piceToSwap := b.get(newRow, newCol)
|
||||||
|
|
||||||
|
b.set(b.empty[0], b.empty[1], piceToSwap)
|
||||||
|
b.set(newRow, newCol, 0)
|
||||||
|
|
||||||
|
b.empty[0] = newRow
|
||||||
|
b.empty[1] = newCol
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Board) Shuffle(steps int) []Direction {
|
||||||
|
moves := []Direction{}
|
||||||
|
|
||||||
|
for i := 0; i < steps; i++ {
|
||||||
|
possibleDirections := b.PossibleDirections()
|
||||||
|
|
||||||
|
// Remove opposite moves to prevent
|
||||||
|
// moving around one cell
|
||||||
|
if len(moves) != 0 {
|
||||||
|
last := moves[len(moves)-1]
|
||||||
|
possibleDirections = slices.DeleteFunc(
|
||||||
|
possibleDirections,
|
||||||
|
func(direction Direction) bool {
|
||||||
|
return oppositeDirections(direction, last)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rand.Shuffle(len(possibleDirections), func(i, j int) {
|
||||||
|
possibleDirections[i], possibleDirections[j] = possibleDirections[j], possibleDirections[i]
|
||||||
|
})
|
||||||
|
|
||||||
|
nextMove := possibleDirections[0]
|
||||||
|
|
||||||
|
b.Move(nextMove)
|
||||||
|
|
||||||
|
moves = append(moves, nextMove)
|
||||||
|
}
|
||||||
|
|
||||||
|
return moves
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimistic number. Indicates
|
||||||
|
// sum of number of moves each board piece should do
|
||||||
|
// to get to desired position. It ignores real "circular"
|
||||||
|
// moves and calculates moves as if only one piece exists on the board.
|
||||||
|
func (board *Board) neededMoves() int {
|
||||||
|
neededMoves := 0
|
||||||
|
|
||||||
|
for row := 0; row < 4; row++ {
|
||||||
|
for col := 0; col < 4; col++ {
|
||||||
|
|
||||||
|
number := board.get(row, col)
|
||||||
|
|
||||||
|
if number == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
neededMoves += rectilinearDistance(number, row, col)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return neededMoves
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Board) get(row, col int) int {
|
||||||
|
return b.grid[row*ROW_COUNT+col]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Board) set(row, col, val int) {
|
||||||
|
b.grid[row*ROW_COUNT+col] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
func originalPosition(number int) (int, int) {
|
||||||
|
return (number - 1) / 4, (number - 1) % 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or "Manhattan distance". We use it to calculate "shortest" path
|
||||||
|
// to desired piece position.
|
||||||
|
// https://en.wikipedia.org/wiki/Taxicab_geometry
|
||||||
|
func rectilinearDistance(number, i, j int) int {
|
||||||
|
origRow, origCol := originalPosition(number)
|
||||||
|
return int(math.Abs(float64(origRow-i)) + math.Abs(float64(origCol-j)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func directionToStep(d Direction) (int, int) {
|
||||||
|
switch d {
|
||||||
|
case UP:
|
||||||
|
return -1, 0
|
||||||
|
case DOWN:
|
||||||
|
return 1, 0
|
||||||
|
case LEFT:
|
||||||
|
return 0, -1
|
||||||
|
case RIGHT:
|
||||||
|
return 0, 1
|
||||||
|
default:
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func oppositeDirections(a Direction, b Direction) bool {
|
||||||
|
ar, al := directionToStep(a)
|
||||||
|
br, bl := directionToStep(b)
|
||||||
|
|
||||||
|
return ar+br == 0 && al+bl == 0
|
||||||
|
}
|
122
lib/board_test.go
Normal file
122
lib/board_test.go
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSolvedState(t *testing.T) {
|
||||||
|
board := NewBoard()
|
||||||
|
if !board.Solved() {
|
||||||
|
t.Error("Initial state should be solved")
|
||||||
|
}
|
||||||
|
|
||||||
|
boardWithShuffledPieces := Board{grid: [16]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0, 15}, empty: [2]int{3, 3}}
|
||||||
|
|
||||||
|
if boardWithShuffledPieces.Solved() {
|
||||||
|
t.Error("Shuffled board should not be solved")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSolvedFast(t *testing.T) {
|
||||||
|
board := NewBoard()
|
||||||
|
if !board.SolvedFast() {
|
||||||
|
t.Error("Initial state should be solved")
|
||||||
|
}
|
||||||
|
|
||||||
|
boardWithShuffledPieces := Board{
|
||||||
|
grid: [16]int{
|
||||||
|
1, 2, 3, 4,
|
||||||
|
5, 6, 7, 8,
|
||||||
|
9, 10, 11, 12,
|
||||||
|
13, 14, 0, 15,
|
||||||
|
},
|
||||||
|
empty: [2]int{3, 3}}
|
||||||
|
|
||||||
|
if boardWithShuffledPieces.SolvedFast() {
|
||||||
|
t.Error("Shuffled board should not be solved")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPossibleDirections(t *testing.T) {
|
||||||
|
board := NewBoard()
|
||||||
|
directions := board.PossibleDirections()
|
||||||
|
|
||||||
|
if len(directions) != 2 {
|
||||||
|
t.Error("For initial state only UP and LEFT directions should be available")
|
||||||
|
}
|
||||||
|
|
||||||
|
isUPresent := slices.Index(directions, UP)
|
||||||
|
isLeftPresent := slices.Index(directions, LEFT)
|
||||||
|
|
||||||
|
if isLeftPresent == -1 || isUPresent == -1 {
|
||||||
|
t.Error("UP and LEFT directions should be present")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMove(t *testing.T) {
|
||||||
|
board := NewBoard()
|
||||||
|
board.Move(LEFT)
|
||||||
|
|
||||||
|
toTheRight := board.grid == [16]int{
|
||||||
|
1, 2, 3, 4,
|
||||||
|
5, 6, 7, 8,
|
||||||
|
9, 10, 11, 12,
|
||||||
|
13, 14, 0, 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !toTheRight {
|
||||||
|
t.Error("Move should move pieces")
|
||||||
|
}
|
||||||
|
|
||||||
|
if board.empty != [2]int{3, 2} {
|
||||||
|
t.Error("after Move new empty position should be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
board.Move(UP)
|
||||||
|
board.Move(UP)
|
||||||
|
board.Move(UP)
|
||||||
|
|
||||||
|
tripleUp := board.grid == [16]int{
|
||||||
|
1, 2, 0, 4,
|
||||||
|
5, 6, 3, 8,
|
||||||
|
9, 10, 7, 12,
|
||||||
|
13, 14, 11, 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tripleUp {
|
||||||
|
t.Error("Corrupt state after moving")
|
||||||
|
}
|
||||||
|
|
||||||
|
board.Move(UP)
|
||||||
|
|
||||||
|
asBefore := board.grid == [16]int{
|
||||||
|
1, 2, 0, 4,
|
||||||
|
5, 6, 3, 8,
|
||||||
|
9, 10, 7, 12,
|
||||||
|
13, 14, 11, 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !asBefore {
|
||||||
|
t.Error("If we cannot move further state should stay the same")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOppositeDirections(t *testing.T) {
|
||||||
|
vertical := oppositeDirections(UP, DOWN)
|
||||||
|
horizontal := oppositeDirections(LEFT, RIGHT)
|
||||||
|
|
||||||
|
if !vertical || !horizontal {
|
||||||
|
t.Error("Opposite direction should return true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShuffle(t *testing.T) {
|
||||||
|
board := NewBoard()
|
||||||
|
board.Shuffle(2)
|
||||||
|
|
||||||
|
if board.SolvedFast() {
|
||||||
|
t.Error("Board should be in unsolved state after shuffle")
|
||||||
|
}
|
||||||
|
}
|
58
lib/game.go
Normal file
58
lib/game.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"atomicgo.dev/keyboard"
|
||||||
|
"atomicgo.dev/keyboard/keys"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Game struct {
|
||||||
|
board *Board
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) PrintState() {
|
||||||
|
// Works only on Linux
|
||||||
|
fmt.Print("\033[H\033[2J")
|
||||||
|
fmt.Printf("To quit game press ESC.\n")
|
||||||
|
g.board.Print()
|
||||||
|
fmt.Printf("Solved: %t\n", g.board.SolvedFast())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) Loop() {
|
||||||
|
g.PrintState()
|
||||||
|
|
||||||
|
keyboard.Listen(func(key keys.Key) (stop bool, err error) {
|
||||||
|
switch key.Code {
|
||||||
|
case keys.CtrlC, keys.Escape:
|
||||||
|
return true, nil
|
||||||
|
case keys.Up:
|
||||||
|
g.board.Move(DOWN)
|
||||||
|
case keys.Down:
|
||||||
|
g.board.Move(UP)
|
||||||
|
case keys.Left:
|
||||||
|
g.board.Move(RIGHT)
|
||||||
|
case keys.Right:
|
||||||
|
g.board.Move(LEFT)
|
||||||
|
default:
|
||||||
|
fmt.Printf("\rYou pressed: %s\n", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.PrintState()
|
||||||
|
|
||||||
|
if g.board.SolvedFast() {
|
||||||
|
fmt.Printf("\rYou Won!\n")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start() {
|
||||||
|
game := Game{board: NewBoard()}
|
||||||
|
|
||||||
|
game.board.Shuffle(10)
|
||||||
|
|
||||||
|
game.Loop()
|
||||||
|
}
|
Loading…
Reference in a new issue