79
README.md
|
@ -2,6 +2,16 @@
|
|||
|
||||
Golang Tetris for console window with optional AI
|
||||
|
||||
## Features include
|
||||
|
||||
- AI (use i key to toggle)
|
||||
- Lock delay
|
||||
- Next piece
|
||||
- Ghost piece
|
||||
- Top scores
|
||||
- Board choices
|
||||
- Edit boards
|
||||
|
||||
## Compile
|
||||
|
||||
```
|
||||
|
@ -13,21 +23,6 @@ go install github.com/MichaelS11/go-tetris
|
|||
|
||||
Then run the binary created, go-tetris or go-tetris.exe
|
||||
|
||||
## Keys during game
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| ← | left move |
|
||||
| z | left rotate |
|
||||
| x | right rotate |
|
||||
| → | right move |
|
||||
| ↓ | soft drop |
|
||||
| ↑ | hard drop |
|
||||
| spacebar | hard drop |
|
||||
| p | pause |
|
||||
| q | quit |
|
||||
| i | toggle AI |
|
||||
|
||||
## Keys start screen
|
||||
|
||||
| Key | Action |
|
||||
|
@ -35,26 +30,58 @@ Then run the binary created, go-tetris or go-tetris.exe
|
|||
| ← | previous board |
|
||||
| → | next board |
|
||||
| spacebar | start game |
|
||||
| ctrl e | edit board |
|
||||
| q | quit |
|
||||
|
||||
## Features include
|
||||
## Keys during game
|
||||
|
||||
- AI (use i key to toggle)
|
||||
- Lock delay
|
||||
- Next piece
|
||||
- Ghost piece
|
||||
- Top scores
|
||||
- Board choices
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| ← | left move |
|
||||
| → | right move |
|
||||
| ↓ | soft drop |
|
||||
| ↑ | hard drop |
|
||||
| spacebar | hard drop |
|
||||
| z | left rotate |
|
||||
| x | right rotate |
|
||||
| p | pause |
|
||||
| q | quit |
|
||||
| i | toggle AI |
|
||||
|
||||
## Keys edit mode
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| ← | move cursor left |
|
||||
| → | move cursor right |
|
||||
| ↓ | move cursor down |
|
||||
| ↑ | move cursor up |
|
||||
| z | rotate left |
|
||||
| x | rotate right |
|
||||
| c | cyan block - I |
|
||||
| b | blue block - J |
|
||||
| w | white block - L |
|
||||
| e | yellow block - O |
|
||||
| g | green block - S |
|
||||
| a | magenta block - T |
|
||||
| r | red block - Z |
|
||||
| f | free block |
|
||||
| ctrl b | change board size |
|
||||
| ctrl s | save board |
|
||||
| ctrl n | save board as new |
|
||||
| ctrl k | delete board |
|
||||
| ctrl o | empty board |
|
||||
| ctrl q | quit edit mode |
|
||||
|
||||
## Screenshots
|
||||
|
||||
![alt text](https://raw.githubusercontent.com/MichaelS11/tetris/master/screenshots/screenshot1.png "Go Tetris")
|
||||
![alt text](https://raw.githubusercontent.com/MichaelS11/tetris/master/screenshots/tetris.png "Go Tetris")
|
||||
|
||||
![alt text](https://raw.githubusercontent.com/MichaelS11/tetris/master/screenshots/screenshot2.png "Golang Tetris")
|
||||
![alt text](https://raw.githubusercontent.com/MichaelS11/tetris/master/screenshots/heart.png "Golang Tetris Heart")
|
||||
|
||||
![alt text](https://raw.githubusercontent.com/MichaelS11/tetris/master/screenshots/screenshot3.png "Golang Tetris Heart")
|
||||
![alt text](https://raw.githubusercontent.com/MichaelS11/tetris/master/screenshots/editmode.png "Edit Mode Peace Symbol")
|
||||
|
||||
![alt text](https://raw.githubusercontent.com/MichaelS11/tetris/master/screenshots/screenshot4.png "Tetris High Scores")
|
||||
![alt text](https://raw.githubusercontent.com/MichaelS11/tetris/master/screenshots/highscores.png "Tetris High Scores")
|
||||
|
||||
## To do
|
||||
|
||||
|
|
89
board.go
|
@ -13,24 +13,73 @@ func NewBoard() {
|
|||
board.Clear()
|
||||
}
|
||||
|
||||
// Clear clears the board
|
||||
// ChangeBoardSize changes board size
|
||||
func ChangeBoardSize(width int, height int) {
|
||||
if board.width == width && board.height == height {
|
||||
return
|
||||
}
|
||||
|
||||
newBoard := &Board{width: width, height: height, boardsIndex: board.boardsIndex}
|
||||
newBoard.colors = make([][]termbox.Attribute, width)
|
||||
for i := 0; i < width; i++ {
|
||||
newBoard.colors[i] = make([]termbox.Attribute, height)
|
||||
for j := 0; j < height; j++ {
|
||||
if i < board.width && j < board.height {
|
||||
newBoard.colors[i][j] = board.colors[i][j]
|
||||
} else {
|
||||
newBoard.colors[i][j] = blankColor
|
||||
}
|
||||
}
|
||||
}
|
||||
newBoard.rotation = make([][]int, width)
|
||||
for i := 0; i < width; i++ {
|
||||
newBoard.rotation[i] = make([]int, height)
|
||||
for j := 0; j < height; j++ {
|
||||
if i < board.width && j < board.height {
|
||||
newBoard.rotation[i][j] = board.rotation[i][j]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
board = newBoard
|
||||
board.previewMino = NewMino()
|
||||
board.currentMino = NewMino()
|
||||
}
|
||||
|
||||
// Clear clears the board to orginal state
|
||||
func (board *Board) Clear() {
|
||||
board.width = len(boards[board.boardsIndex].colors)
|
||||
board.height = len(boards[board.boardsIndex].colors[0])
|
||||
board.colors = make([][]termbox.Attribute, len(boards[board.boardsIndex].colors))
|
||||
for i := 0; i < len(boards[board.boardsIndex].colors); i++ {
|
||||
board.colors[i] = make([]termbox.Attribute, len(boards[board.boardsIndex].colors[i]))
|
||||
board.colors = make([][]termbox.Attribute, board.width)
|
||||
for i := 0; i < board.width; i++ {
|
||||
board.colors[i] = make([]termbox.Attribute, board.height)
|
||||
copy(board.colors[i], boards[board.boardsIndex].colors[i])
|
||||
}
|
||||
board.rotation = make([][]int, len(boards[board.boardsIndex].rotation))
|
||||
for i := 0; i < len(boards[board.boardsIndex].rotation); i++ {
|
||||
board.rotation[i] = make([]int, len(boards[board.boardsIndex].rotation[i]))
|
||||
board.rotation = make([][]int, board.width)
|
||||
for i := 0; i < board.width; i++ {
|
||||
board.rotation[i] = make([]int, board.height)
|
||||
copy(board.rotation[i], boards[board.boardsIndex].rotation[i])
|
||||
}
|
||||
board.previewMino = NewMino()
|
||||
board.currentMino = NewMino()
|
||||
}
|
||||
|
||||
// EmptyBoard removes all blocks/colors from the board
|
||||
func (board *Board) EmptyBoard() {
|
||||
for i := 0; i < board.width; i++ {
|
||||
for j := 0; j < board.height; j++ {
|
||||
board.colors[i][j] = blankColor
|
||||
}
|
||||
}
|
||||
for i := 0; i < board.width; i++ {
|
||||
for j := 0; j < board.height; j++ {
|
||||
board.rotation[i][j] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PreviousBoard switches to previous board
|
||||
func (board *Board) PreviousBoard() {
|
||||
board.boardsIndex--
|
||||
|
@ -247,9 +296,30 @@ func (board *Board) deleteLine(line int) {
|
|||
// SetColor sets the color and rotation of board location
|
||||
func (board *Board) SetColor(x int, y int, color termbox.Attribute, rotation int) {
|
||||
board.colors[x][y] = color
|
||||
if rotation < 0 {
|
||||
return
|
||||
}
|
||||
board.rotation[x][y] = rotation
|
||||
}
|
||||
|
||||
// RotateLeft rotates cell left
|
||||
func (board *Board) RotateLeft(x int, y int) {
|
||||
if board.rotation[x][y] == 0 {
|
||||
board.rotation[x][y] = 3
|
||||
return
|
||||
}
|
||||
board.rotation[x][y]--
|
||||
}
|
||||
|
||||
// RotateRight rotates cell right
|
||||
func (board *Board) RotateRight(x int, y int) {
|
||||
if board.rotation[x][y] == 3 {
|
||||
board.rotation[x][y] = 0
|
||||
return
|
||||
}
|
||||
board.rotation[x][y]++
|
||||
}
|
||||
|
||||
// ValidBlockLocation checks if block location is vaild
|
||||
func (board *Board) ValidBlockLocation(x int, y int, mustBeOnBoard bool) bool {
|
||||
if x < 0 || x >= board.width || y >= board.height {
|
||||
|
@ -311,6 +381,11 @@ func (board *Board) DrawDropMino() {
|
|||
mino.DrawMino(MinoDrop)
|
||||
}
|
||||
|
||||
// DrawCursor draws the edit cursor
|
||||
func (board *Board) DrawCursor(x int, y int) {
|
||||
view.DrawCursor(x, y, board.colors[x][y])
|
||||
}
|
||||
|
||||
// printDebugBoard is for printing board in text for debuging
|
||||
func (board *Board) printDebugBoard() {
|
||||
for j := 0; j < board.height; j++ {
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
// NewEdit creates a new edit mode
|
||||
func NewEdit() {
|
||||
edit = &Edit{moved: true}
|
||||
}
|
||||
|
||||
// EnabledEditMode enable edit mode
|
||||
func (edit *Edit) EnabledEditMode() {
|
||||
if edit.y > board.height-1 {
|
||||
edit.y = board.height - 1
|
||||
}
|
||||
if edit.x > board.width-1 {
|
||||
edit.x = board.width - 1
|
||||
}
|
||||
edit.moved = true
|
||||
}
|
||||
|
||||
// DisableEditMode disable edit mode
|
||||
func (edit *Edit) DisableEditMode() {
|
||||
err := saveUserBoards()
|
||||
if err != nil {
|
||||
logger.Fatal("error saving user boards:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// BoardSizeMode changed to board size edit mode
|
||||
func (edit *Edit) BoardSizeMode() {
|
||||
edit.width = board.width
|
||||
edit.height = board.height
|
||||
edit.boardSize = true
|
||||
}
|
||||
|
||||
// BoardWidthIncrement board width increment
|
||||
func (edit *Edit) BoardWidthIncrement() {
|
||||
if edit.width > 39 {
|
||||
return
|
||||
}
|
||||
edit.width++
|
||||
}
|
||||
|
||||
// BoardWidthDecrement board width decrement
|
||||
func (edit *Edit) BoardWidthDecrement() {
|
||||
if edit.width < 9 {
|
||||
return
|
||||
}
|
||||
edit.width--
|
||||
}
|
||||
|
||||
// BoardHeightIncrement board height increment
|
||||
func (edit *Edit) BoardHeightIncrement() {
|
||||
if edit.height > 39 {
|
||||
return
|
||||
}
|
||||
edit.height++
|
||||
}
|
||||
|
||||
// BoardHeightDecrement board height decrement
|
||||
func (edit *Edit) BoardHeightDecrement() {
|
||||
if edit.height < 9 {
|
||||
return
|
||||
}
|
||||
edit.height--
|
||||
}
|
||||
|
||||
// ChangeBoardSize create new board
|
||||
func (edit *Edit) ChangeBoardSize() {
|
||||
ChangeBoardSize(edit.width, edit.height)
|
||||
edit.boardSize = false
|
||||
}
|
||||
|
||||
// EmptyBoard removes all blocks/colors from the board
|
||||
func (edit *Edit) EmptyBoard() {
|
||||
board.EmptyBoard()
|
||||
}
|
||||
|
||||
// CursorUp move cursor up
|
||||
func (edit *Edit) CursorUp() {
|
||||
if !edit.moved {
|
||||
edit.moved = true
|
||||
}
|
||||
if edit.y < 1 {
|
||||
return
|
||||
}
|
||||
edit.y--
|
||||
}
|
||||
|
||||
// CursorDown move cursor down
|
||||
func (edit *Edit) CursorDown() {
|
||||
if !edit.moved {
|
||||
edit.moved = true
|
||||
}
|
||||
if edit.y == board.height-1 {
|
||||
return
|
||||
}
|
||||
edit.y++
|
||||
}
|
||||
|
||||
// CursorRight move cursor right
|
||||
func (edit *Edit) CursorRight() {
|
||||
if !edit.moved {
|
||||
edit.moved = true
|
||||
}
|
||||
if edit.x == board.width-1 {
|
||||
return
|
||||
}
|
||||
edit.x++
|
||||
}
|
||||
|
||||
// CursorLeft move cursor left
|
||||
func (edit *Edit) CursorLeft() {
|
||||
if !edit.moved {
|
||||
edit.moved = true
|
||||
}
|
||||
if edit.x < 1 {
|
||||
return
|
||||
}
|
||||
edit.x--
|
||||
}
|
||||
|
||||
// SetColor sets the board color
|
||||
func (edit *Edit) SetColor(color termbox.Attribute) {
|
||||
if edit.moved {
|
||||
edit.moved = false
|
||||
}
|
||||
if edit.saved {
|
||||
edit.saved = false
|
||||
}
|
||||
board.SetColor(edit.x, edit.y, color, -1)
|
||||
}
|
||||
|
||||
// RotateLeft rotates cell left
|
||||
func (edit *Edit) RotateLeft() {
|
||||
if edit.moved {
|
||||
edit.moved = false
|
||||
}
|
||||
if edit.saved {
|
||||
edit.saved = false
|
||||
}
|
||||
board.RotateLeft(edit.x, edit.y)
|
||||
}
|
||||
|
||||
// RotateRight rotates cell right
|
||||
func (edit *Edit) RotateRight() {
|
||||
if edit.moved {
|
||||
edit.moved = false
|
||||
}
|
||||
if edit.saved {
|
||||
edit.saved = false
|
||||
}
|
||||
board.RotateRight(edit.x, edit.y)
|
||||
}
|
||||
|
||||
// DrawCursor draws the cursor location when cursor moves
|
||||
func (edit *Edit) DrawCursor() {
|
||||
if !edit.moved {
|
||||
return
|
||||
}
|
||||
board.DrawCursor(edit.x, edit.y)
|
||||
}
|
||||
|
||||
// SaveBoard save board
|
||||
func (edit *Edit) SaveBoard() {
|
||||
if board.boardsIndex < numInternalBoards {
|
||||
edit.SaveBoardNew()
|
||||
return
|
||||
}
|
||||
boards[board.boardsIndex].colors = board.colors
|
||||
boards[board.boardsIndex].rotation = board.rotation
|
||||
if !edit.saved {
|
||||
edit.saved = true
|
||||
}
|
||||
}
|
||||
|
||||
// SaveBoardNew save board as new board
|
||||
func (edit *Edit) SaveBoardNew() {
|
||||
aBoards := Boards{name: time.Now().Format("Jan 2 3:4:5")}
|
||||
aBoards.colors = make([][]termbox.Attribute, len(board.colors))
|
||||
for i := 0; i < len(board.colors); i++ {
|
||||
aBoards.colors[i] = append(aBoards.colors[i], board.colors[i]...)
|
||||
}
|
||||
aBoards.rotation = make([][]int, len(board.rotation))
|
||||
for i := 0; i < len(board.rotation); i++ {
|
||||
aBoards.rotation[i] = append(aBoards.rotation[i], board.rotation[i]...)
|
||||
}
|
||||
boards = append(boards, aBoards)
|
||||
board.boardsIndex = len(boards) - 1
|
||||
if !edit.saved {
|
||||
edit.saved = true
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteBoard deletes a board
|
||||
func (edit *Edit) DeleteBoard() {
|
||||
if board.boardsIndex < numInternalBoards {
|
||||
return
|
||||
}
|
||||
boards = append(boards[:board.boardsIndex], boards[board.boardsIndex+1:]...)
|
||||
board.boardsIndex--
|
||||
board.Clear()
|
||||
}
|
18
engine.go
|
@ -48,7 +48,7 @@ loop:
|
|||
engine.tick()
|
||||
case <-engine.aiTimer.C:
|
||||
engine.ai.ProcessQueue()
|
||||
engine.aiTimer.Reset(engine.tickTime / 6)
|
||||
engine.aiTimer.Reset(engine.tickTime / aiTickDivider)
|
||||
case <-engine.chanStop:
|
||||
break loop
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ func (engine *Engine) Pause() {
|
|||
func (engine *Engine) UnPause() {
|
||||
engine.timer.Reset(engine.tickTime)
|
||||
if engine.aiEnabled {
|
||||
engine.aiTimer.Reset(engine.tickTime / 6)
|
||||
engine.aiTimer.Reset(engine.tickTime / aiTickDivider)
|
||||
}
|
||||
engine.paused = false
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ loop:
|
|||
func (engine *Engine) EnabledAi() {
|
||||
engine.aiEnabled = true
|
||||
go engine.ai.GetBestQueue()
|
||||
engine.aiTimer.Reset(engine.tickTime / 6)
|
||||
engine.aiTimer.Reset(engine.tickTime / aiTickDivider)
|
||||
}
|
||||
|
||||
// DisableAi disables the AI
|
||||
|
@ -259,3 +259,15 @@ func (engine *Engine) DisableAi() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EnabledEditMode enables edit mode
|
||||
func (engine *Engine) EnabledEditMode() {
|
||||
edit.EnabledEditMode()
|
||||
engine.editMode = true
|
||||
}
|
||||
|
||||
// DisableEditMode disables edit mode
|
||||
func (engine *Engine) DisableEditMode() {
|
||||
edit.DisableEditMode()
|
||||
engine.editMode = false
|
||||
}
|
||||
|
|
43
globals.go
|
@ -8,10 +8,12 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
blankColor = termbox.ColorBlack
|
||||
boardXOffset = 4
|
||||
boardYOffset = 2
|
||||
rankingFileName = "/tetris.db"
|
||||
blankColor = termbox.ColorBlack
|
||||
boardXOffset = 4
|
||||
boardYOffset = 2
|
||||
aiTickDivider = 8
|
||||
rankingFileName = "/go-tetris.db"
|
||||
settingsFileName = "/go-tetris.json"
|
||||
|
||||
// MinoPreview is for the preview mino
|
||||
MinoPreview MinoType = iota
|
||||
|
@ -57,12 +59,20 @@ type (
|
|||
dropDistance int
|
||||
}
|
||||
|
||||
// Boards holds all the premade boards
|
||||
// Boards holds all the boards
|
||||
Boards struct {
|
||||
name string
|
||||
colors [][]termbox.Attribute
|
||||
rotation [][]int
|
||||
}
|
||||
|
||||
// BoardsJSON is for JSON format of boards
|
||||
BoardsJSON struct {
|
||||
Name string
|
||||
Mino [][]string
|
||||
Rotation [][]int
|
||||
}
|
||||
|
||||
// KeyInput is the key input engine
|
||||
KeyInput struct {
|
||||
stopped bool
|
||||
|
@ -103,16 +113,35 @@ type (
|
|||
ai *Ai
|
||||
aiEnabled bool
|
||||
aiTimer *time.Timer
|
||||
editMode bool
|
||||
}
|
||||
|
||||
// Edit is the board edit mode
|
||||
Edit struct {
|
||||
x int
|
||||
y int
|
||||
moved bool
|
||||
boardSize bool
|
||||
width int
|
||||
height int
|
||||
saved bool
|
||||
}
|
||||
|
||||
// Settings is the JSON load/save file
|
||||
Settings struct {
|
||||
Boards []BoardsJSON
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
boards []Boards
|
||||
|
||||
baseDir string
|
||||
logger *log.Logger
|
||||
minos *Minos
|
||||
board *Board
|
||||
view *View
|
||||
engine *Engine
|
||||
edit *Edit
|
||||
|
||||
boards []Boards
|
||||
numInternalBoards int
|
||||
)
|
||||
|
|
93
keyInput.go
|
@ -40,7 +40,7 @@ loop:
|
|||
|
||||
// ProcessEvent process the key input event
|
||||
func (keyInput *KeyInput) ProcessEvent(event *termbox.Event) {
|
||||
if event.Key == termbox.KeyCtrlI {
|
||||
if event.Key == termbox.KeyCtrlL {
|
||||
// Ctrl l (lower case L) to log stack trace
|
||||
buffer := make([]byte, 1<<16)
|
||||
length := runtime.Stack(buffer, true)
|
||||
|
@ -49,6 +49,73 @@ func (keyInput *KeyInput) ProcessEvent(event *termbox.Event) {
|
|||
return
|
||||
}
|
||||
|
||||
if engine.editMode {
|
||||
if edit.boardSize {
|
||||
switch event.Ch {
|
||||
case 0:
|
||||
switch event.Key {
|
||||
case termbox.KeyArrowUp:
|
||||
edit.BoardHeightIncrement()
|
||||
case termbox.KeyArrowDown:
|
||||
edit.BoardHeightDecrement()
|
||||
case termbox.KeyArrowLeft:
|
||||
edit.BoardWidthDecrement()
|
||||
case termbox.KeyArrowRight:
|
||||
edit.BoardWidthIncrement()
|
||||
}
|
||||
case 'q':
|
||||
edit.ChangeBoardSize()
|
||||
}
|
||||
} else {
|
||||
switch event.Ch {
|
||||
case 0:
|
||||
switch event.Key {
|
||||
case termbox.KeyArrowUp:
|
||||
edit.CursorUp()
|
||||
case termbox.KeyArrowDown:
|
||||
edit.CursorDown()
|
||||
case termbox.KeyArrowLeft:
|
||||
edit.CursorLeft()
|
||||
case termbox.KeyArrowRight:
|
||||
edit.CursorRight()
|
||||
case termbox.KeyCtrlB:
|
||||
edit.BoardSizeMode()
|
||||
case termbox.KeyCtrlS:
|
||||
edit.SaveBoard()
|
||||
case termbox.KeyCtrlN:
|
||||
edit.SaveBoardNew()
|
||||
case termbox.KeyCtrlK:
|
||||
edit.DeleteBoard()
|
||||
case termbox.KeyCtrlO:
|
||||
edit.EmptyBoard()
|
||||
case termbox.KeyCtrlQ, termbox.KeyCtrlC:
|
||||
engine.DisableEditMode()
|
||||
}
|
||||
case 'c':
|
||||
edit.SetColor(termbox.ColorCyan)
|
||||
case 'b':
|
||||
edit.SetColor(termbox.ColorBlue)
|
||||
case 'w':
|
||||
edit.SetColor(termbox.ColorWhite)
|
||||
case 'e':
|
||||
edit.SetColor(termbox.ColorYellow)
|
||||
case 'g':
|
||||
edit.SetColor(termbox.ColorGreen)
|
||||
case 'a':
|
||||
edit.SetColor(termbox.ColorMagenta)
|
||||
case 'r':
|
||||
edit.SetColor(termbox.ColorRed)
|
||||
case 'f':
|
||||
edit.SetColor(blankColor)
|
||||
case 'z':
|
||||
edit.RotateLeft()
|
||||
case 'x':
|
||||
edit.RotateRight()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if event.Ch == 'q' || event.Key == termbox.KeyCtrlC {
|
||||
if !keyInput.stopped {
|
||||
keyInput.stopped = true
|
||||
|
@ -67,6 +134,8 @@ func (keyInput *KeyInput) ProcessEvent(event *termbox.Event) {
|
|||
board.PreviousBoard()
|
||||
case termbox.KeyArrowRight:
|
||||
board.NextBoard()
|
||||
case termbox.KeyCtrlE:
|
||||
engine.EnabledEditMode()
|
||||
}
|
||||
}
|
||||
return
|
||||
|
@ -89,7 +158,8 @@ func (keyInput *KeyInput) ProcessEvent(event *termbox.Event) {
|
|||
return
|
||||
}
|
||||
|
||||
if event.Ch == 0 {
|
||||
switch event.Ch {
|
||||
case 0:
|
||||
switch event.Key {
|
||||
case termbox.KeySpace:
|
||||
board.MinoDrop()
|
||||
|
@ -102,17 +172,14 @@ func (keyInput *KeyInput) ProcessEvent(event *termbox.Event) {
|
|||
case termbox.KeyArrowRight:
|
||||
board.MinoMoveRight()
|
||||
}
|
||||
} else {
|
||||
switch event.Ch {
|
||||
case 'z':
|
||||
board.MinoRotateLeft()
|
||||
case 'x':
|
||||
board.MinoRotateRight()
|
||||
case 'p':
|
||||
engine.Pause()
|
||||
case 'i':
|
||||
engine.EnabledAi()
|
||||
}
|
||||
case 'z':
|
||||
board.MinoRotateLeft()
|
||||
case 'x':
|
||||
board.MinoRotateRight()
|
||||
case 'p':
|
||||
engine.Pause()
|
||||
case 'i':
|
||||
engine.EnabledAi()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,11 @@ func setupForTesting() {
|
|||
|
||||
rand.Seed(1)
|
||||
|
||||
err := loadBoards()
|
||||
if err != nil {
|
||||
log.Fatal("error loading boards:", err)
|
||||
}
|
||||
|
||||
NewMinos()
|
||||
NewBoard()
|
||||
NewEngine()
|
||||
|
|
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 6.0 KiB |
14
tetris.go
|
@ -13,20 +13,30 @@ func main() {
|
|||
logger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.LUTC|log.Llongfile)
|
||||
logFile, err := os.OpenFile(baseDir+"/go-tetris.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
log.Fatal("error opening logFile:", err)
|
||||
logger.Fatal("error opening log file:", err)
|
||||
}
|
||||
defer logFile.Close()
|
||||
logger.SetOutput(logFile)
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
err = loadBoards()
|
||||
if err != nil {
|
||||
logger.Fatal("error loading internal boards:", err)
|
||||
}
|
||||
|
||||
err = loadUserBoards()
|
||||
if err != nil {
|
||||
logger.Fatal("error loading user boards:", err)
|
||||
}
|
||||
|
||||
NewMinos()
|
||||
NewBoard()
|
||||
NewView()
|
||||
NewEngine()
|
||||
NewEdit()
|
||||
|
||||
engine.Run()
|
||||
|
||||
view.Stop()
|
||||
|
||||
}
|
||||
|
|
205
view.go
|
@ -32,29 +32,53 @@ func (view *View) Stop() {
|
|||
func (view *View) RefreshScreen() {
|
||||
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
|
||||
|
||||
view.drawBackground()
|
||||
view.drawBoardBoarder()
|
||||
|
||||
if engine.editMode {
|
||||
if edit.boardSize {
|
||||
board.DrawBoard()
|
||||
view.drawEditTextsBoardSize()
|
||||
} else {
|
||||
board.DrawBoard()
|
||||
edit.DrawCursor()
|
||||
view.drawEditTexts()
|
||||
}
|
||||
termbox.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
view.drawPreviewBoarder()
|
||||
view.drawTexts()
|
||||
|
||||
if engine.previewBoard {
|
||||
board.DrawBoard()
|
||||
view.drawGameOver()
|
||||
} else if engine.gameOver {
|
||||
view.drawGameOver()
|
||||
view.drawRankingScores()
|
||||
} else if engine.paused {
|
||||
view.drawPaused()
|
||||
} else {
|
||||
board.DrawBoard()
|
||||
board.DrawPreviewMino()
|
||||
board.DrawDropMino()
|
||||
board.DrawCurrentMino()
|
||||
termbox.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
if engine.gameOver {
|
||||
view.drawGameOver()
|
||||
view.drawRankingScores()
|
||||
termbox.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
if engine.paused {
|
||||
view.drawPaused()
|
||||
termbox.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
board.DrawBoard()
|
||||
board.DrawPreviewMino()
|
||||
board.DrawDropMino()
|
||||
board.DrawCurrentMino()
|
||||
termbox.Flush()
|
||||
}
|
||||
|
||||
// drawBackground draws the background
|
||||
func (view *View) drawBackground() {
|
||||
// drawBoard draws the board boarder
|
||||
func (view *View) drawBoardBoarder() {
|
||||
// playing board
|
||||
xOffset := boardXOffset
|
||||
yOffset := boardYOffset
|
||||
|
@ -70,12 +94,14 @@ func (view *View) drawBackground() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// piece preview
|
||||
xOffset = boardXOffset + board.width*2 + 8
|
||||
yOffset = boardYOffset
|
||||
xEnd = xOffset + 14
|
||||
yEnd = yOffset + 6
|
||||
// drawPreviewBoarder draws the preview boarder
|
||||
func (view *View) drawPreviewBoarder() {
|
||||
xOffset := boardXOffset + board.width*2 + 8
|
||||
yOffset := boardYOffset
|
||||
xEnd := xOffset + 14
|
||||
yEnd := yOffset + 6
|
||||
for x := xOffset; x < xEnd; x++ {
|
||||
for y := yOffset; y < yEnd; y++ {
|
||||
if x == xOffset || x == xOffset+1 || x == xEnd-1 || x == xEnd-2 ||
|
||||
|
@ -112,10 +138,6 @@ func (view *View) drawTexts() {
|
|||
// ascii arrow characters add extra two spaces
|
||||
view.drawText(xOffset, yOffset, "← - left", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "z - rotate left", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "x - rotate right", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "→ - right", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "↓ - soft drop", termbox.ColorWhite, termbox.ColorBlack)
|
||||
|
@ -124,21 +146,116 @@ func (view *View) drawTexts() {
|
|||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "sbar - hard drop", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "z - rotate left", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "x - rotate right", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "p - pause", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "q - quit", termbox.ColorWhite, termbox.ColorBlack)
|
||||
}
|
||||
|
||||
// drawEditTexts draws the edit text
|
||||
func (view *View) drawEditTexts() {
|
||||
xOffset := boardXOffset + board.width*2 + 8
|
||||
yOffset := boardYOffset
|
||||
|
||||
view.drawText(xOffset, yOffset, "Name:", termbox.ColorWhite, termbox.ColorBlue)
|
||||
view.drawText(xOffset+7, yOffset, boards[board.boardsIndex].name, termbox.ColorBlack, termbox.ColorWhite)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "Saved:", termbox.ColorWhite, termbox.ColorBlue)
|
||||
if edit.saved {
|
||||
view.drawText(xOffset+7, yOffset, "yes", termbox.ColorBlack, termbox.ColorWhite)
|
||||
} else {
|
||||
view.drawText(xOffset+7, yOffset, "no", termbox.ColorBlack, termbox.ColorWhite)
|
||||
}
|
||||
|
||||
yOffset += 2
|
||||
|
||||
// ascii arrow characters add extra two spaces
|
||||
view.drawText(xOffset, yOffset, "← - left", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "→ - right", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "↓ - down", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "↑ - up", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "z - rotate left", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "x - rotate right", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "c - cyan", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "b - blue", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "w - white", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "e - yellow", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "g - green", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "a - magenta", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "r - red", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "f - free", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "ctrl b - change board size", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "ctrl s - save board", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "ctrl n - save board as new", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "ctrl k - delete board", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "ctrl o - empty board", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "ctrl q - quit", termbox.ColorWhite, termbox.ColorBlack)
|
||||
}
|
||||
|
||||
// drawEditTextsBoardSize draws the edit text for board size mode
|
||||
func (view *View) drawEditTextsBoardSize() {
|
||||
xOffset := boardXOffset + board.width*2 + 8
|
||||
yOffset := boardYOffset
|
||||
|
||||
view.drawText(xOffset, yOffset, "Name:", termbox.ColorWhite, termbox.ColorBlue)
|
||||
view.drawText(xOffset+7, yOffset, boards[board.boardsIndex].name, termbox.ColorBlack, termbox.ColorWhite)
|
||||
|
||||
yOffset += 2
|
||||
|
||||
view.drawText(xOffset, yOffset, "Size:", termbox.ColorWhite, termbox.ColorBlue)
|
||||
view.drawText(xOffset+7, yOffset, fmt.Sprintf("%2d X %2d", edit.width, edit.height), termbox.ColorBlack, termbox.ColorWhite)
|
||||
|
||||
yOffset += 2
|
||||
|
||||
// ascii arrow characters add extra two spaces
|
||||
view.drawText(xOffset, yOffset, "← - board width decrement", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "→ - board width increment", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "↓ - board height decrement", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "↑ - board height increment", termbox.ColorWhite, termbox.ColorBlack)
|
||||
yOffset++
|
||||
view.drawText(xOffset, yOffset, "q - done", termbox.ColorWhite, termbox.ColorBlack)
|
||||
}
|
||||
|
||||
// DrawPreviewMinoBlock draws the preview mino
|
||||
func (view *View) DrawPreviewMinoBlock(x int, y int, color termbox.Attribute, rotation int, length int) {
|
||||
var char1 rune
|
||||
var char2 rune
|
||||
if rotation < 2 {
|
||||
char1 = '▓'
|
||||
char2 = ' '
|
||||
} else {
|
||||
char1 = ' '
|
||||
char2 = '▓'
|
||||
char1 := ' '
|
||||
char2 := ' '
|
||||
switch rotation {
|
||||
case 0:
|
||||
char1 = '▄'
|
||||
char2 = '▄'
|
||||
case 1:
|
||||
char1 = '█'
|
||||
case 2:
|
||||
char1 = '▀'
|
||||
char2 = '▀'
|
||||
case 3:
|
||||
char2 = '█'
|
||||
}
|
||||
xOffset := 2*x + 2*board.width + boardXOffset + 11 + (4 - length)
|
||||
termbox.SetCell(xOffset, y+boardYOffset+2, char1, color, color^termbox.AttrBold)
|
||||
|
@ -147,14 +264,19 @@ func (view *View) DrawPreviewMinoBlock(x int, y int, color termbox.Attribute, ro
|
|||
|
||||
// DrawBlock draws a block
|
||||
func (view *View) DrawBlock(x int, y int, color termbox.Attribute, rotation int) {
|
||||
var char1 rune
|
||||
var char2 rune
|
||||
if rotation < 2 {
|
||||
char1 = '▓'
|
||||
char2 = ' '
|
||||
} else {
|
||||
char1 = ' '
|
||||
char2 = '▓'
|
||||
char1 := ' '
|
||||
char2 := ' '
|
||||
switch rotation {
|
||||
case 0:
|
||||
char1 = '▄'
|
||||
char2 = '▄'
|
||||
case 1:
|
||||
char1 = '█'
|
||||
case 2:
|
||||
char1 = '▀'
|
||||
char2 = '▀'
|
||||
case 3:
|
||||
char2 = '█'
|
||||
}
|
||||
if color == blankColor {
|
||||
// blankColor means drop Mino
|
||||
|
@ -304,3 +426,10 @@ func (view *View) colorizeLine(y int, color termbox.Attribute) {
|
|||
termbox.SetCell(x*2+boardXOffset+3, y+boardYOffset+1, ' ', termbox.ColorDefault, color)
|
||||
}
|
||||
}
|
||||
|
||||
// DrawCursor draws current cursor location
|
||||
func (view *View) DrawCursor(x int, y int, color termbox.Attribute) {
|
||||
termbox.SetCell(x*2+boardXOffset+2, y+boardYOffset+1, '◄', color^termbox.AttrBold, termbox.ColorBlack^termbox.AttrBold)
|
||||
termbox.SetCell(x*2+boardXOffset+3, y+boardYOffset+1, '►', color^termbox.AttrBold, termbox.ColorBlack^termbox.AttrBold)
|
||||
termbox.Flush()
|
||||
}
|
||||
|
|