Add piece holding
This commit is contained in:
parent
2c3e4c9541
commit
4174bfc21c
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue