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