Add piece holding

This commit is contained in:
Shadowfacts 2019-10-15 23:17:47 -04:00
parent 2c3e4c9541
commit 4174bfc21c
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
5 changed files with 110 additions and 15 deletions

View File

@ -22,8 +22,20 @@ struct ContentView: View {
var body: some View { var body: some View {
GeometryReader { (geometry) in GeometryReader { (geometry) in
VStack { VStack {
HStack {
VStack(spacing: 0) {
Text("Held")
if self.controller.heldTetromino != nil {
Group {
TetrominoView(tetromino: self.controller.heldTetromino!)
}.frame(width: 50, height: 50, alignment: .center)
} else {
Rectangle().foregroundColor(.clear).frame(width: 50, height: 50)
}
}
}
BoardView(board: self.$controller.board, currentPiece: self.$controller.currentPiece, droppedPiece: self.$controller.currentPieceAtDropPoint) BoardView(board: self.$controller.board, currentPiece: self.$controller.currentPiece, droppedPiece: self.$controller.currentPieceAtDropPoint)
.aspectRatio(CGSize(width: 10, height: 16), contentMode: .fit) .aspectRatio(CGSize(width: self.controller.width, height: self.controller.height), contentMode: .fit)
.onAppear(perform: self.startTimer) .onAppear(perform: self.startTimer)
.onDisappear(perform: self.stopTimer) .onDisappear(perform: self.stopTimer)
.onTapGesture(perform: self.onTap) .onTapGesture(perform: self.onTap)
@ -32,9 +44,15 @@ struct ContentView: View {
// .gesture(horizDragGesture.simultaneously(with: verticalDragGesture)) // .gesture(horizDragGesture.simultaneously(with: verticalDragGesture))
HStack { HStack {
Spacer()
Button(action: self.controller.hold) {
Image(systemName: "arrow.up.square.fill").resizable().frame(width: 50, height: 50)
}
Spacer()
Button(action: self.onTap) { Button(action: self.onTap) {
Image(systemName: "goforward").resizable().frame(width: 50, height: 50) Image(systemName: "goforward").resizable().frame(width: 50, height: 50)
} }
Spacer()
} }
HStack { HStack {
Button(action: self.controller.left) { Button(action: self.controller.left) {
@ -107,7 +125,7 @@ struct ContentView: View {
} }
func onSwipeUp() { func onSwipeUp() {
// hold self.controller.hold()
} }
func onSwipeDown() { func onSwipeDown() {

View File

@ -24,6 +24,7 @@ public class GameController: ObservableObject {
} }
} }
@Published public var currentPieceAtDropPoint: GamePiece? @Published public var currentPieceAtDropPoint: GamePiece?
@Published public var heldTetromino: Tetromino?
public var ended: Bool { public var ended: Bool {
return (0..<width).first(where: { board[$0, 0] }) != nil return (0..<width).first(where: { board[$0, 0] }) != nil
@ -40,8 +41,8 @@ public class GameController: ObservableObject {
} }
func nextPiece() { func nextPiece() {
currentPiece = GamePiece(tetromino: .random()) let tetromino = Tetromino.random()
currentPiece!.topLeft = ((width - currentPiece!.tiles.count) / 2, 0) currentPiece = GamePiece(tetromino: tetromino, topLeft: ((width - tetromino.shape.count) / 2, 0))
} }
func finalizePiece() { func finalizePiece() {
@ -49,9 +50,7 @@ public class GameController: ObservableObject {
clearLines() clearLines()
nextPiece() nextPiece()
if case .playing(.dropped) = state { state = .playing(.normal)
state = .playing(.normal)
}
} }
func clearLines() { func clearLines() {
@ -80,7 +79,8 @@ public class GameController: ObservableObject {
} }
public func rotate(direction: RotationDirection) { public func rotate(direction: RotationDirection) {
guard case .playing(_) = state, guard case let .playing(playState) = state,
playState != .dropped,
let currentPiece = currentPiece else { return } let currentPiece = currentPiece else { return }
let modifiedPiece = currentPiece.rotated(direction) let modifiedPiece = currentPiece.rotated(direction)
@ -90,7 +90,8 @@ public class GameController: ObservableObject {
} }
public func left() { public func left() {
guard case .playing(.normal) = state, guard case let .playing(playState) = state,
playState != .dropped,
let currentPiece = currentPiece else { return } let currentPiece = currentPiece else { return }
let modifiedPiece = currentPiece.moved(by: (-1, 0)) let modifiedPiece = currentPiece.moved(by: (-1, 0))
if !overlapsAny(modifiedPiece) { if !overlapsAny(modifiedPiece) {
@ -99,7 +100,8 @@ public class GameController: ObservableObject {
} }
public func right() { public func right() {
guard case .playing(.normal) = state, guard case let .playing(playState) = state,
playState != .dropped,
let currentPiece = currentPiece else { return } let currentPiece = currentPiece else { return }
let modifiedPiece = currentPiece.moved(by: (1, 0)) let modifiedPiece = currentPiece.moved(by: (1, 0))
if !overlapsAny(modifiedPiece) { if !overlapsAny(modifiedPiece) {
@ -108,11 +110,24 @@ public class GameController: ObservableObject {
} }
public func drop() { public func drop() {
guard case .playing(.normal) = state else { return } guard case let .playing(playState) = state, playState != .dropped else { return }
currentPiece = currentPieceAtDropPoint currentPiece = currentPieceAtDropPoint
state = .playing(.dropped) state = .playing(.dropped)
} }
public func hold() {
guard case .playing(.normal) = state,
let currentPiece = currentPiece else { return }
if let heldTetromino = heldTetromino {
self.heldTetromino = currentPiece.tetromino
self.currentPiece = GamePiece(tetromino: heldTetromino, topLeft: ((width - heldTetromino.shape.count) / 2, 0))
} else {
heldTetromino = currentPiece.tetromino
nextPiece()
}
state = .playing(.switched)
}
private func updateCurrentPieceAtDropPoint() { private func updateCurrentPieceAtDropPoint() {
guard let currentPiece = currentPiece else { return } guard let currentPiece = currentPiece else { return }
var prev = currentPiece var prev = currentPiece
@ -146,4 +161,5 @@ enum GameState {
enum PlayState { enum PlayState {
case normal case normal
case dropped case dropped
case switched
} }

View File

@ -13,10 +13,10 @@ public struct GamePiece {
public var topLeft: (Int, Int) public var topLeft: (Int, Int)
public internal(set) var tiles: [[Bool]] public internal(set) var tiles: [[Bool]]
public init(tetromino: Tetromino) { public init(tetromino: Tetromino, topLeft: (Int, Int) = (0, 0)) {
self.tetromino = tetromino self.tetromino = tetromino
self.tiles = tetromino.shape self.tiles = tetromino.shape
self.topLeft = (0, 0) self.topLeft = topLeft
} }
mutating func rotate(direction: RotationDirection) { mutating func rotate(direction: RotationDirection) {

View File

@ -23,9 +23,9 @@ struct GridView<Cell>: View where Cell: View {
var body: some View { var body: some View {
GeometryReader { (geometry) in GeometryReader { (geometry) in
VStack(spacing: 0) { VStack(spacing: 0) {
ForEach(0..<self.rows) { (row) in ForEach(0..<self.rows, id: \.self) { (row) in
HStack(spacing: 0) { HStack(spacing: 0) {
ForEach(0..<self.columns) { (col) in ForEach(0..<self.columns, id: \.self) { (col) in
self.cellProvider(col, row, self.cellSize(for: geometry)) self.cellProvider(col, row, self.cellSize(for: geometry))
} }
} }

View File

@ -0,0 +1,61 @@
//
// TetrominoView.swift
// Tetris
//
// Created by Shadowfacts on 10/15/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import SwiftUI
import TetrisKit
public struct TetrominoView: View {
let tetromino: Tetromino
public init(tetromino: Tetromino) {
self.tetromino = tetromino
}
public var body: some View {
GridView(rows: self.rows, columns: self.columns) { (col, row, size) in
Rectangle()
.foregroundColor(self.colorAt(col, row))
.frame(width: size, height: size)
}
}
var rows: Int {
self.tetromino.shape.firstIndex(where: { row in
row.allSatisfy({ el in
!el
})
}) ?? self.tetromino.shape.count
}
var columns: Int {
self.tetromino.shape.map { row in
(row.firstIndex(where: { el in
!el
}) ?? row.count - 1) + 1
}.max() ?? self.tetromino.shape.first!.count
}
func colorAt(_ col: Int, _ row: Int) -> Color {
if row < tetromino.shape.count && col < tetromino.shape[row].count && tetromino.shape[row][col] {
return tetromino.color
} else {
return .clear
}
}
}
struct TetrominoView_Previews: PreviewProvider {
static var previews: some View {
Group {
TetrominoView(tetromino: .t)
TetrominoView(tetromino: .i)
TetrominoView(tetromino: .z)
TetrominoView(tetromino: .j)
}
}
}