go-tetris/ai.go

254 lines
5.5 KiB
Go

package main
// NewAi creates a new AI
func NewAi() *Ai {
ai := Ai{}
queue := make([]rune, 1)
queue[0] = 'x'
ai.queue = &queue
return &ai
}
// ProcessQueue checks AI queue and process key moments
func (ai *Ai) ProcessQueue() {
if ai.newQueue != nil {
ai.queue = ai.newQueue
ai.newQueue = nil
ai.index = 0
}
queue := *ai.queue
// wasd + qe keyboard keys
switch 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
}
ai.index++
view.RefreshScreen()
}
// GetBestQueue gets the best queue
func (ai *Ai) GetBestQueue() {
bestScore := -9999999
bestQueue := []rune{'x'}
currentMino := board.currentMino
previewMino := board.previewMino
rotations1 := 5
rotations2 := 5
slides := 5
if board.width > 10 {
slides = 3
}
switch currentMino.minoRotation[0][1][1] {
case colorCyan, colorGreen, colorRed:
rotations1 = 2
case colorYellow:
rotations1 = 1
}
switch previewMino.minoRotation[0][1][1] {
case colorCyan, colorGreen, colorRed:
rotations2 = 2
case colorYellow:
rotations2 = 1
}
for slide1 := 0; slide1 < slides; slide1++ {
for move1 := board.width; move1 >= 0; move1-- {
for rotate1 := 0; rotate1 < rotations1; rotate1++ {
queue, mino1 := board.getMovesforMino(rotate1, move1, slide1, currentMino, nil)
if mino1 == nil {
continue
}
for slide2 := 0; slide2 < slides; slide2++ {
for move2 := board.width; move2 >= 0; move2-- {
for rotate2 := 0; rotate2 < rotations2; rotate2++ {
_, mino2 := board.getMovesforMino(rotate2, move2, slide2, previewMino, mino1)
if mino2 == nil {
continue
}
fullLines, holes, bumpy, heightEnds := board.boardStatsWithMinos(mino1, mino2)
score := ai.getScoreFromBoardStats(fullLines, holes, bumpy, heightEnds, mino1.y, mino2.y)
if score > bestScore {
bestScore = score
bestQueue = queue
}
}
}
}
}
}
}
ai.newQueue = &bestQueue
}
func (board *Board) getMovesforMino(rotate int, move int, slide int, mino1 *Mino, mino2 *Mino) ([]rune, *Mino) {
var i int
queue := make([]rune, 0, (rotate/2+1)+(move/2+1)+(slide/2+1)+1)
mino := *mino1
mino.MoveDown()
if rotate%2 == 0 {
rotate /= 2
for i = 0; i < rotate; i++ {
mino.RotateRight()
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
return queue, nil
}
queue = append(queue, 'e')
}
} else {
rotate = rotate/2 + 1
for i = 0; i < rotate; i++ {
mino.RotateLeft()
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
return queue, nil
}
queue = append(queue, 'q')
}
}
if move%2 == 0 {
move /= 2
for i = 0; i < move; i++ {
mino.MoveRight()
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
return queue, nil
}
queue = append(queue, 'd')
}
} else {
move = move/2 + 1
for i = 0; i < move; i++ {
mino.MoveLeft()
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
return queue, nil
}
queue = append(queue, 'a')
}
}
for mino.ValidLocation(false) && (mino2 == nil || !mino2.minoOverlap(&mino)) {
mino.MoveDown()
}
mino.MoveUp()
queue = append(queue, 'w')
if slide%2 == 0 {
slide /= 2
for i = 0; i < slide; i++ {
mino.MoveLeft()
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
return queue, nil
}
queue = append(queue, 'a')
}
} else {
slide = slide/2 + 1
for i = 0; i < slide; i++ {
mino.MoveRight()
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
return queue, nil
}
queue = append(queue, 'd')
}
}
if !mino.ValidLocation(true) {
return queue, nil
}
return append(queue, 'x'), &mino
}
func (board *Board) boardStatsWithMinos(mino1 *Mino, mino2 *Mino) (fullLines int, holes int, bumpy int, heightEnds int) {
var i int
var j int
// fullLines
for j = 0; j < board.height; j++ {
board.fullLinesY[j] = true
for i = 0; i < board.width; i++ {
if board.colors[i][j] == colorBlank && !mino1.isMinoAtLocation(i, j) && !mino2.isMinoAtLocation(i, j) {
board.fullLinesY[j] = false
break
}
}
if board.fullLinesY[j] {
fullLines++
}
}
// holes and bumpy
var foundLast int
var fullLinesFound int
for i = 0; i < board.width; i++ {
found := board.height
fullLinesFound = 0
for j = 0; j < board.height; j++ {
if board.fullLinesY[j] {
fullLinesFound++
} else {
if board.colors[i][j] != colorBlank || mino1.isMinoAtLocation(i, j) || mino2.isMinoAtLocation(i, j) {
found = j
break
}
}
}
if i == 0 {
heightEnds = board.height - (found + fullLines - fullLinesFound)
} else {
diffrence := (found + fullLines - fullLinesFound) - foundLast
if diffrence < 0 {
diffrence = -diffrence
}
bumpy += diffrence
}
foundLast = found + fullLines - fullLinesFound
for j++; j < board.height; j++ {
if board.colors[i][j] == colorBlank && !mino1.isMinoAtLocation(i, j) && !mino2.isMinoAtLocation(i, j) {
holes++
}
}
}
heightEnds += board.height - foundLast
return
}
func (ai *Ai) getScoreFromBoardStats(fullLines int, holes int, bumpy int, heightEnds int, height1 int, height2 int) int {
score := 8 * heightEnds
if fullLines > 3 {
score += 512
}
score -= 75 * holes
score -= 15 * bumpy
if height1 < 6 {
score -= 10 * (5 - height1)
}
if height2 < 6 {
score -= 10 * (5 - height2)
}
return score
}