forked from LoveDaisy/tetris_game
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tetris_ai.py
147 lines (125 loc) · 5.34 KB
/
tetris_ai.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from tetris_model import BOARD_DATA, Shape
import math
from datetime import datetime
import numpy as np
class TetrisAI(object):
def nextMove(self):
t1 = datetime.now()
if BOARD_DATA.currentShape == Shape.shapeNone:
return None
currentDirection = BOARD_DATA.currentDirection
currentY = BOARD_DATA.currentY
_, _, minY, _ = BOARD_DATA.nextShape.getBoundingOffsets(0)
nextY = -minY
# print("=======")
strategy = None
if BOARD_DATA.currentShape.shape in (Shape.shapeI, Shape.shapeZ, Shape.shapeS):
d0Range = (0, 1)
elif BOARD_DATA.currentShape.shape == Shape.shapeO:
d0Range = (0,)
else:
d0Range = (0, 1, 2, 3)
if BOARD_DATA.nextShape.shape in (Shape.shapeI, Shape.shapeZ, Shape.shapeS):
d1Range = (0, 1)
elif BOARD_DATA.nextShape.shape == Shape.shapeO:
d1Range = (0,)
else:
d1Range = (0, 1, 2, 3)
for d0 in d0Range:
minX, maxX, _, _ = BOARD_DATA.currentShape.getBoundingOffsets(d0)
for x0 in range(-minX, BOARD_DATA.width - maxX):
board = self.calcStep1Board(d0, x0)
for d1 in d1Range:
minX, maxX, _, _ = BOARD_DATA.nextShape.getBoundingOffsets(d1)
dropDist = self.calcNextDropDist(board, d1, range(-minX, BOARD_DATA.width - maxX))
for x1 in range(-minX, BOARD_DATA.width - maxX):
score = self.calculateScore(np.copy(board), d1, x1, dropDist)
if not strategy or strategy[2] < score:
strategy = (d0, x0, score)
print("===", datetime.now() - t1)
return strategy
def calcNextDropDist(self, data, d0, xRange):
res = {}
for x0 in xRange:
if x0 not in res:
res[x0] = BOARD_DATA.height - 1
for x, y in BOARD_DATA.nextShape.getCoords(d0, x0, 0):
yy = 0
while yy + y < BOARD_DATA.height and (yy + y < 0 or data[(y + yy), x] == Shape.shapeNone):
yy += 1
yy -= 1
if yy < res[x0]:
res[x0] = yy
return res
def calcStep1Board(self, d0, x0):
board = np.array(BOARD_DATA.getData()).reshape((BOARD_DATA.height, BOARD_DATA.width))
self.dropDown(board, BOARD_DATA.currentShape, d0, x0)
return board
def dropDown(self, data, shape, direction, x0):
dy = BOARD_DATA.height - 1
for x, y in shape.getCoords(direction, x0, 0):
yy = 0
while yy + y < BOARD_DATA.height and (yy + y < 0 or data[(y + yy), x] == Shape.shapeNone):
yy += 1
yy -= 1
if yy < dy:
dy = yy
# print("dropDown: shape {0}, direction {1}, x0 {2}, dy {3}".format(shape.shape, direction, x0, dy))
self.dropDownByDist(data, shape, direction, x0, dy)
def dropDownByDist(self, data, shape, direction, x0, dist):
for x, y in shape.getCoords(direction, x0, 0):
data[y + dist, x] = shape.shape
def calculateScore(self, step1Board, d1, x1, dropDist):
# print("calculateScore")
t1 = datetime.now()
width = BOARD_DATA.width
height = BOARD_DATA.height
self.dropDownByDist(step1Board, BOARD_DATA.nextShape, d1, x1, dropDist[x1])
# print(datetime.now() - t1)
# Term 1: lines to be removed
fullLines, nearFullLines = 0, 0
roofY = [0] * width
holeCandidates = [0] * width
holeConfirm = [0] * width
vHoles, vBlocks = 0, 0
for y in range(height - 1, -1, -1):
hasHole = False
hasBlock = False
for x in range(width):
if step1Board[y, x] == Shape.shapeNone:
hasHole = True
holeCandidates[x] += 1
else:
hasBlock = True
roofY[x] = height - y
if holeCandidates[x] > 0:
holeConfirm[x] += holeCandidates[x]
holeCandidates[x] = 0
if holeConfirm[x] > 0:
vBlocks += 1
if not hasBlock:
break
if not hasHole and hasBlock:
fullLines += 1
vHoles = sum([x ** .7 for x in holeConfirm])
maxHeight = max(roofY) - fullLines
# print(datetime.now() - t1)
roofDy = [roofY[i] - roofY[i+1] for i in range(len(roofY) - 1)]
if len(roofY) <= 0:
stdY = 0
else:
stdY = math.sqrt(sum([y ** 2 for y in roofY]) / len(roofY) - (sum(roofY) / len(roofY)) ** 2)
if len(roofDy) <= 0:
stdDY = 0
else:
stdDY = math.sqrt(sum([y ** 2 for y in roofDy]) / len(roofDy) - (sum(roofDy) / len(roofDy)) ** 2)
absDy = sum([abs(x) for x in roofDy])
maxDy = max(roofY) - min(roofY)
# print(datetime.now() - t1)
score = fullLines * 1.8 - vHoles * 1.0 - vBlocks * 0.5 - maxHeight ** 1.5 * 0.02 \
- stdY * 0.0 - stdDY * 0.01 - absDy * 0.2 - maxDy * 0.3
# print(score, fullLines, vHoles, vBlocks, maxHeight, stdY, stdDY, absDy, roofY, d0, x0, d1, x1)
return score
TETRIS_AI = TetrisAI()