go-tetris/ai.go

254 lines
5.5 KiB
Go
Raw Normal View History

2017-03-27 15:07:29 -05:00
package main
2018-10-02 20:21:08 -05:00
// NewAi creates a new AI
2017-03-27 15:07:29 -05:00
func NewAi() *Ai {
ai := Ai{}
queue := make([]rune, 1)
queue[0] = 'x'
ai.queue = &queue
2017-03-27 15:07:29 -05:00
return &ai
}
2018-10-02 20:21:08 -05:00
// 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
2017-03-27 15:07:29 -05:00
// wasd + qe keyboard keys
switch queue[ai.index] {
2017-03-27 15:07:29 -05:00
case 'w':
board.MinoDrop()
case 'a':
board.MinoMoveLeft()
case 'd':
board.MinoMoveRight()
case 'q':
board.MinoRotateLeft()
case 'e':
board.MinoRotateRight()
case 'x':
return
2017-03-27 15:07:29 -05:00
}
ai.index++
view.RefreshScreen()
}
2018-10-02 20:21:08 -05:00
// GetBestQueue gets the best queue
2017-03-27 15:07:29 -05:00
func (ai *Ai) GetBestQueue() {
bestScore := -9999999
2019-01-14 18:11:46 -06:00
bestQueue := []rune{'x'}
currentMino := board.currentMino
previewMino := board.previewMino
2018-05-25 21:02:00 -05:00
rotations1 := 5
rotations2 := 5
2019-01-14 18:11:46 -06:00
slides := 5
if board.width > 10 {
slides = 3
}
2018-05-25 21:02:00 -05:00
switch currentMino.minoRotation[0][1][1] {
2019-12-01 15:03:27 -06:00
case colorCyan, colorGreen, colorRed:
2018-05-25 21:02:00 -05:00
rotations1 = 2
2019-12-01 15:03:27 -06:00
case colorYellow:
2018-05-25 21:02:00 -05:00
rotations1 = 1
}
2019-01-14 18:11:46 -06:00
switch previewMino.minoRotation[0][1][1] {
2019-12-01 15:03:27 -06:00
case colorCyan, colorGreen, colorRed:
2018-05-25 21:02:00 -05:00
rotations2 = 2
2019-12-01 15:03:27 -06:00
case colorYellow:
2018-05-25 21:02:00 -05:00
rotations2 = 1
}
2017-03-27 15:07:29 -05:00
2019-01-14 18:11:46 -06:00
for slide1 := 0; slide1 < slides; slide1++ {
for move1 := board.width; move1 >= 0; move1-- {
2018-05-25 21:02:00 -05:00
for rotate1 := 0; rotate1 < rotations1; rotate1++ {
2017-03-27 15:07:29 -05:00
2019-01-14 18:11:46 -06:00
queue, mino1 := board.getMovesforMino(rotate1, move1, slide1, currentMino, nil)
2017-03-27 15:07:29 -05:00
if mino1 == nil {
continue
}
2019-01-14 18:11:46 -06:00
for slide2 := 0; slide2 < slides; slide2++ {
for move2 := board.width; move2 >= 0; move2-- {
2018-05-25 21:02:00 -05:00
for rotate2 := 0; rotate2 < rotations2; rotate2++ {
2017-03-27 15:07:29 -05:00
2019-01-14 18:11:46 -06:00
_, mino2 := board.getMovesforMino(rotate2, move2, slide2, previewMino, mino1)
2017-03-27 15:07:29 -05:00
if mino2 == nil {
continue
}
2019-01-14 18:11:46 -06:00
fullLines, holes, bumpy, heightEnds := board.boardStatsWithMinos(mino1, mino2)
score := ai.getScoreFromBoardStats(fullLines, holes, bumpy, heightEnds, mino1.y, mino2.y)
2017-03-27 15:07:29 -05:00
if score > bestScore {
2017-03-27 15:07:29 -05:00
bestScore = score
bestQueue = queue
}
}
}
}
}
}
}
ai.newQueue = &bestQueue
2017-03-27 15:07:29 -05:00
}
func (board *Board) getMovesforMino(rotate int, move int, slide int, mino1 *Mino, mino2 *Mino) ([]rune, *Mino) {
2019-01-14 18:11:46 -06:00
var i int
queue := make([]rune, 0, (rotate/2+1)+(move/2+1)+(slide/2+1)+1)
mino := *mino1
2019-01-14 18:11:46 -06:00
mino.MoveDown()
if rotate%2 == 0 {
rotate /= 2
2019-01-14 18:11:46 -06:00
for i = 0; i < rotate; i++ {
2017-03-27 15:07:29 -05:00
mino.RotateRight()
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
2017-03-27 15:07:29 -05:00
return queue, nil
}
2019-01-14 18:11:46 -06:00
queue = append(queue, 'e')
2017-03-27 15:07:29 -05:00
}
} else {
rotate = rotate/2 + 1
2019-01-14 18:11:46 -06:00
for i = 0; i < rotate; i++ {
2017-03-27 15:07:29 -05:00
mino.RotateLeft()
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
2017-03-27 15:07:29 -05:00
return queue, nil
}
2019-01-14 18:11:46 -06:00
queue = append(queue, 'q')
2017-03-27 15:07:29 -05:00
}
}
if move%2 == 0 {
move /= 2
2019-01-14 18:11:46 -06:00
for i = 0; i < move; i++ {
mino.MoveRight()
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
2017-03-27 15:07:29 -05:00
return queue, nil
}
2019-01-14 18:11:46 -06:00
queue = append(queue, 'd')
2017-03-27 15:07:29 -05:00
}
} else {
move = move/2 + 1
2019-01-14 18:11:46 -06:00
for i = 0; i < move; i++ {
mino.MoveLeft()
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
2017-03-27 15:07:29 -05:00
return queue, nil
}
2019-01-14 18:11:46 -06:00
queue = append(queue, 'a')
2017-03-27 15:07:29 -05:00
}
}
for mino.ValidLocation(false) && (mino2 == nil || !mino2.minoOverlap(&mino)) {
2017-03-27 15:07:29 -05:00
mino.MoveDown()
}
mino.MoveUp()
queue = append(queue, 'w')
if slide%2 == 0 {
slide /= 2
2019-01-14 18:11:46 -06:00
for i = 0; i < slide; i++ {
2017-03-27 15:07:29 -05:00
mino.MoveLeft()
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
2017-03-27 15:07:29 -05:00
return queue, nil
}
2019-01-14 18:11:46 -06:00
queue = append(queue, 'a')
2017-03-27 15:07:29 -05:00
}
} else {
slide = slide/2 + 1
2019-01-14 18:11:46 -06:00
for i = 0; i < slide; i++ {
2017-03-27 15:07:29 -05:00
mino.MoveRight()
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
2017-03-27 15:07:29 -05:00
return queue, nil
}
2019-01-14 18:11:46 -06:00
queue = append(queue, 'd')
2017-03-27 15:07:29 -05:00
}
}
if !mino.ValidLocation(true) {
return queue, nil
2017-03-27 15:07:29 -05:00
}
2019-01-14 18:11:46 -06:00
return append(queue, 'x'), &mino
2017-03-27 15:07:29 -05:00
}
2019-01-14 18:11:46 -06:00
func (board *Board) boardStatsWithMinos(mino1 *Mino, mino2 *Mino) (fullLines int, holes int, bumpy int, heightEnds int) {
2019-01-11 20:39:41 -06:00
var i int
var j int
2019-01-14 18:11:46 -06:00
2017-03-27 15:07:29 -05:00
// fullLines
2019-01-11 20:39:41 -06:00
for j = 0; j < board.height; j++ {
board.fullLinesY[j] = true
for i = 0; i < board.width; i++ {
2019-12-01 15:03:27 -06:00
if board.colors[i][j] == colorBlank && !mino1.isMinoAtLocation(i, j) && !mino2.isMinoAtLocation(i, j) {
2019-01-11 20:39:41 -06:00
board.fullLinesY[j] = false
2017-03-27 15:07:29 -05:00
break
}
}
2019-01-11 20:39:41 -06:00
if board.fullLinesY[j] {
2017-03-27 15:07:29 -05:00
fullLines++
}
}
2017-03-28 14:52:09 -05:00
// holes and bumpy
2019-01-14 18:11:46 -06:00
var foundLast int
var fullLinesFound int
2019-01-11 20:39:41 -06:00
for i = 0; i < board.width; i++ {
2019-01-14 18:11:46 -06:00
found := board.height
fullLinesFound = 0
2019-01-11 20:39:41 -06:00
for j = 0; j < board.height; j++ {
if board.fullLinesY[j] {
2019-01-14 18:11:46 -06:00
fullLinesFound++
2017-03-27 15:07:29 -05:00
} else {
2019-12-01 15:03:27 -06:00
if board.colors[i][j] != colorBlank || mino1.isMinoAtLocation(i, j) || mino2.isMinoAtLocation(i, j) {
2019-01-14 18:11:46 -06:00
found = j
2017-03-27 15:07:29 -05:00
break
}
}
}
2019-01-14 18:11:46 -06:00
if i == 0 {
heightEnds = board.height - (found + fullLines - fullLinesFound)
} else {
diffrence := (found + fullLines - fullLinesFound) - foundLast
2017-03-27 15:07:29 -05:00
if diffrence < 0 {
diffrence = -diffrence
}
bumpy += diffrence
}
2019-01-14 18:11:46 -06:00
foundLast = found + fullLines - fullLinesFound
2017-03-27 15:07:29 -05:00
2019-01-14 18:11:46 -06:00
for j++; j < board.height; j++ {
2019-12-01 15:03:27 -06:00
if board.colors[i][j] == colorBlank && !mino1.isMinoAtLocation(i, j) && !mino2.isMinoAtLocation(i, j) {
2017-03-28 14:52:09 -05:00
holes++
2017-03-27 15:07:29 -05:00
}
}
}
2019-01-14 18:11:46 -06:00
heightEnds += board.height - foundLast
2017-03-27 15:07:29 -05:00
return
}
2019-01-14 18:11:46 -06:00
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
2017-03-27 15:07:29 -05:00
}
2019-01-14 18:11:46 -06:00
score -= 75 * holes
score -= 15 * bumpy
if height1 < 6 {
score -= 10 * (5 - height1)
}
if height2 < 6 {
score -= 10 * (5 - height2)
}
2017-03-27 15:07:29 -05:00
return score
}