/
Board.scala
150 lines (126 loc) · 3.5 KB
/
Board.scala
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
147
148
149
150
package com.github.tetrisanalyzer.board
object Board {
val EmptyLine = 0
/**
* Creates an empty board.
*/
def apply(width: Int = 10, height: Int = 20) = {
val lines = Array.fill(height) { EmptyLine }
new Board(width, height, lines)
}
/**
* Create a board from a string representation.
*/
def apply(lines: Array[String]) = {
val width = lines(0).length - 2
val height = lines.length - 1
require(lines(height) == bottomTextLine(width))
val boardLines = Array.tabulate(height) (
((y) => fromText(width, lines(y)))
)
new Board(width, height, boardLines)
}
def bottomTextLine(width: Int) = "#" * (width + 2)
private def fromText(width: Int, textLine: String): Int = {
var line = EmptyLine
for (i <- width to 1 by -1) {
line <<= 1
line |= (if (textLine(i) == '-') 0 else 1)
}
line
}
}
/**
* Represents a Tetris board. Default size is 10x20.
*
* Each line is represented by a 32 bit integer where each bit corresponds to a column on the board.
* Bit 0 corresponds to the x-value 0 (left most position), bit 1 to x-value 1 etc.
*
* This is a highly optimized version that does not follow best practice in object-orientation!
*/
class Board(val width: Int, val height: Int, val lines: Array[Int]) {
private val completeLine = calculateCompleteLine(width)
require(width >= 4 && width <= 32)
require(height >= 4)
private def calculateCompleteLine(width: Int): Int = {
var line = Board.EmptyLine
for (i <- 1 to width) {
line <<= 1
line |= 1
}
line
}
/**
* True if the specified dot is not occupied.
*/
def isFree(x: Int, y: Int) = {
try {
(lines(y) & (1 << x)) == 0
} catch {
case e: IndexOutOfBoundsException => false
}
}
/**
* Clears completed lines and returns which lines that was cleared.
* This method is be called after a piece has been placed on the board.
* pieceY: the y position of the piece.
* pieceHeight: height of the piece.
*/
def clearLines(pieceY: Int, pieceHeight: Int): Int = {
var clearedLines = 0
var y1 = pieceY + pieceHeight
// Find first line to clear
do {
y1 -= 1
if (lines(y1) == completeLine)
clearedLines += 1
} while (clearedLines == 0 && y1 > pieceY)
// Clear lines
if (clearedLines > 0) {
var y2 = y1
while (y1 >= 0) {
y2 -= 1
while (y2 >= pieceY && lines(y2) == completeLine) {
clearedLines += 1
y2 -= 1
}
if (y2 >= 0)
lines(y1) = lines(y2)
else
lines(y1) = Board.EmptyLine
y1 -= 1
}
}
clearedLines
}
/**
* Returns a new copy of this (mutable) board.
*/
def copy: Board = {
val copyLines = new Array[Int](height)
lines.copyToArray(copyLines)
new Board(width, height, copyLines)
}
/**
* Restores this (mutable) bard from a another board.
*/
def restore(other: Board) {
other.lines.copyToArray(lines)
}
override def equals(that: Any) = that match {
case other: Board => lines.toList == other.lines.toList
case _ => false
}
override def toString: String = {
lines.map(boardLineAsString(_)).mkString("\n") + "\n" + Board.bottomTextLine(width)
}
/**
* Converts a board line into its string representation.
*/
private def boardLineAsString(boardLine: Int): String = {
var result = "#"
for (i <- 0 until width)
result += (if (((boardLine >> i) & 1) == 0) "-" else "x")
result + "#"
}
}