go-tetris/ai.go

271 lines
5.7 KiB
Go

package main
const (
aiQueueSize = (boardWidth+1)/2 + 6
aiMoveLength = (boardWidth + 1) / 2
)
type Ai struct {
queue [aiQueueSize]rune
index int
}
func NewAi() *Ai {
ai := Ai{}
for i := 0; i < aiQueueSize; i++ {
ai.queue[i] = 'x'
}
return &ai
}
func (ai *Ai) ProcessQueue() bool {
// wasd + qe keyboard keys
switch ai.queue[ai.index] {
case 'w':
board.MinoDrop()
case 'a':
board.MinoMoveLeft()
case 'd':
board.MinoMoveRight()
case 'q':
board.MinoRotateLeft()
case 'e':
board.MinoRotateRight()
case 'x':
return false
}
ai.index++
if ai.index == aiQueueSize {
ai.index = 0
}
view.RefreshScreen()
return true
}
func (ai *Ai) GetBestQueue() {
ai.addMovesToQueue(ai.getBestQueue())
}
func (ai *Ai) addMovesToQueue(queue []rune) {
insertIndex := ai.index
for _, char := range queue {
ai.queue[insertIndex] = char
insertIndex++
if insertIndex == aiQueueSize {
insertIndex = 0
}
}
}
func (ai *Ai) getBestQueue() []rune {
bestQueue := make([]rune, 0, 0)
bestScore := -9999999
var slideScore int
bestSlide := 6
for move1 := 0; move1 <= boardWidth; move1++ {
for rotate1 := 0; rotate1 < 5; rotate1++ {
for slide1 := 0; slide1 <= 5; slide1++ {
queue, mino1 := ai.getMovesforMino(rotate1, move1, slide1, nil)
if mino1 == nil {
continue
}
for move2 := 0; move2 <= boardWidth; move2++ {
for rotate2 := 0; rotate2 < 5; rotate2++ {
for slide2 := 0; slide2 <= 5; slide2++ {
_, mino2 := ai.getMovesforMino(rotate2, move2, slide2, mino1)
if mino2 == nil {
continue
}
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
score := ai.getScoreFromBoardStats(fullLines, holes, bumpy)
if slide1 < 3 {
slideScore = slide1
} else {
slideScore = slide1 - 2
}
if score > bestScore || (score == bestScore && slideScore < bestSlide) {
bestScore = score
bestQueue = queue
bestSlide = slideScore
}
}
}
}
}
}
}
return bestQueue
}
func (ai *Ai) getMovesforMino(rotate int, move int, slide int, mino1 *Mino) ([]rune, *Mino) {
queue := make([]rune, 0, 4)
var mino Mino
if mino1 != nil {
mino = *board.previewMino
} else {
mino = *board.currentMino
}
if rotate < 3 {
for i := 0; i < rotate; i++ {
mino.RotateRight()
queue = append(queue, 'e')
if !mino.ValidLocation(false) || (mino1 != nil && mino1.minoOverlap(&mino)) {
return queue, nil
}
}
} else {
for i := 0; i < rotate-2; i++ {
mino.RotateLeft()
queue = append(queue, 'q')
if !mino.ValidLocation(false) || (mino1 != nil && mino1.minoOverlap(&mino)) {
return queue, nil
}
}
}
if move <= aiMoveLength {
move = aiMoveLength - move
for i := 0; i < move; i++ {
mino.MoveLeft()
queue = append(queue, 'a')
if !mino.ValidLocation(false) || (mino1 != nil && mino1.minoOverlap(&mino)) {
return queue, nil
}
}
} else {
move = move - aiMoveLength + 1
for i := 0; i < move; i++ {
mino.MoveRight()
queue = append(queue, 'd')
if !mino.ValidLocation(false) || (mino1 != nil && mino1.minoOverlap(&mino)) {
return queue, nil
}
}
}
for mino.ValidLocation(false) && (mino1 == nil || !mino1.minoOverlap(&mino)) {
mino.MoveDown()
}
mino.MoveUp()
queue = append(queue, 'w')
if slide < 3 {
for i := 0; i < slide; i++ {
mino.MoveLeft()
queue = append(queue, 'a')
if !mino.ValidLocation(false) || (mino1 != nil && mino1.minoOverlap(&mino)) {
return queue, nil
}
}
} else {
slide = slide - 2
for i := 0; i < slide; i++ {
mino.MoveRight()
queue = append(queue, 'd')
if !mino.ValidLocation(false) || (mino1 != nil && mino1.minoOverlap(&mino)) {
return queue, nil
}
}
}
queue = append(queue, 'x')
return queue, &mino
}
func (mino *Mino) minoOverlap(mino1 *Mino) bool {
minoBlocks := mino.minoRotation[mino.rotation]
for i := 0; i < mino.length; i++ {
for j := 0; j < mino.length; j++ {
if minoBlocks[i][j] != blankColor {
if mino1.isMinoAtLocation(mino.x+i, mino.y+j) {
return true
}
}
}
}
return false
}
func (board *Board) boardStatsWithMinos(mino1 *Mino, mino2 *Mino) (fullLines int, holes int, bumpy int) {
// fullLines
fullLinesY := make(map[int]bool, 2)
for j := 0; j < boardHeight; j++ {
isFullLine := true
for i := 0; i < boardWidth; i++ {
if board.colors[i][j] == blankColor && !mino1.isMinoAtLocation(i, j) && !mino2.isMinoAtLocation(i, j) {
isFullLine = false
break
}
}
if isFullLine {
fullLinesY[j] = true
fullLines++
}
}
// holes and bumpy
indexLast := 0
for i := 0; i < boardWidth; i++ {
index := boardHeight
indexOffset := 0
for j := 0; j < boardHeight; j++ {
if _, found := fullLinesY[j]; found {
indexOffset++
} else {
if board.colors[i][j] != blankColor || mino1.isMinoAtLocation(i, j) || mino2.isMinoAtLocation(i, j) {
index = j
break
}
}
}
if i != 0 {
diffrence := (index + fullLines - indexOffset) - indexLast
if diffrence < 0 {
diffrence = -diffrence
}
bumpy += diffrence
}
indexLast = index + fullLines - indexOffset
index++
for j := index; j < boardHeight; j++ {
if board.colors[i][j] == blankColor && !mino1.isMinoAtLocation(i, j) && !mino2.isMinoAtLocation(i, j) {
holes++
}
}
}
return
}
func (mino *Mino) isMinoAtLocation(x int, y int) bool {
xIndex := x - mino.x
yIndex := y - mino.y
if xIndex < 0 || xIndex >= mino.length || yIndex < 0 || yIndex >= mino.length {
return false
}
minoBlocks := mino.minoRotation[mino.rotation]
if minoBlocks[xIndex][yIndex] != blankColor {
return true
}
return false
}
func (ai *Ai) getScoreFromBoardStats(fullLines int, holes int, bumpy int) (score int) {
if fullLines == 4 {
score += 256
}
score -= 75 * holes
score -= 25 * bumpy
return score
}