Compare commits
3 Commits
f844d5466f
...
2c3e4c9541
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 2c3e4c9541 | |
Shadowfacts | dec8131898 | |
Shadowfacts | c948ecd587 |
|
@ -17,7 +17,7 @@ struct ContentView: View {
|
||||||
return c
|
return c
|
||||||
}()
|
}()
|
||||||
@State var timer: Timer?
|
@State var timer: Timer?
|
||||||
@State var prevHorizTranslation: CGFloat?
|
@State var initialXPosition: Int?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { (geometry) in
|
GeometryReader { (geometry) in
|
||||||
|
@ -27,7 +27,7 @@ struct ContentView: View {
|
||||||
.onAppear(perform: self.startTimer)
|
.onAppear(perform: self.startTimer)
|
||||||
.onDisappear(perform: self.stopTimer)
|
.onDisappear(perform: self.stopTimer)
|
||||||
.onTapGesture(perform: self.onTap)
|
.onTapGesture(perform: self.onTap)
|
||||||
.gesture(self.horizDragGesture(geometry: geometry))
|
.gesture(self.horizDragGesture(geometry: geometry).simultaneously(with: self.verticalDragGesture))
|
||||||
// .gesture(ExclusiveGesture(horizDragGesture, verticalDragGesture))
|
// .gesture(ExclusiveGesture(horizDragGesture, verticalDragGesture))
|
||||||
// .gesture(horizDragGesture.simultaneously(with: verticalDragGesture))
|
// .gesture(horizDragGesture.simultaneously(with: verticalDragGesture))
|
||||||
|
|
||||||
|
@ -57,32 +57,25 @@ struct ContentView: View {
|
||||||
DragGesture(coordinateSpace: .global)
|
DragGesture(coordinateSpace: .global)
|
||||||
.onChanged { (state) in
|
.onChanged { (state) in
|
||||||
guard let currentPiece = self.controller.currentPiece else { return }
|
guard let currentPiece = self.controller.currentPiece else { return }
|
||||||
// let position = (state.location.x) / geometry.size.width * 10
|
if self.initialXPosition == nil {
|
||||||
// let moved = currentPiece.moved(by: (Int(position.rounded()) - currentPiece.topLeft.0, 0))
|
self.initialXPosition = currentPiece.topLeft.0
|
||||||
// if !self.controller.overlapsAny(moved) {
|
|
||||||
// self.controller.currentPiece = moved
|
|
||||||
// }
|
|
||||||
if self.prevHorizTranslation == nil {
|
|
||||||
self.prevHorizTranslation = state.translation.width
|
|
||||||
}
|
}
|
||||||
let delta = state.translation.width - self.prevHorizTranslation!
|
var moved = currentPiece
|
||||||
let amount = Int((delta / 20).rounded())
|
let xPosition = self.initialXPosition! + Int((state.translation.width / (geometry.size.width / 10)).rounded())
|
||||||
let moved = currentPiece.moved(by: (amount, 0))
|
moved.topLeft = (xPosition, currentPiece.topLeft.1)
|
||||||
if !self.controller.overlapsAny(moved) {
|
if !self.controller.overlapsAny(moved) {
|
||||||
self.controller.currentPiece = moved
|
self.controller.currentPiece = moved
|
||||||
self.prevHorizTranslation = state.translation.width
|
|
||||||
}
|
}
|
||||||
}.onEnded { (state) in
|
}.onEnded { (state) in
|
||||||
self.prevHorizTranslation = nil
|
self.initialXPosition = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var verticalDragGesture: some Gesture {
|
var verticalDragGesture: some Gesture {
|
||||||
DragGesture()
|
DragGesture()
|
||||||
.onEnded { (state) in
|
.onEnded { (state) in
|
||||||
let deltaY = state.location.y - state.startLocation.y
|
if abs(state.translation.height) > 80 {
|
||||||
if abs(deltaY) > 40 {
|
if state.translation.height > 0 {
|
||||||
if deltaY > 0 {
|
|
||||||
self.onSwipeDown()
|
self.onSwipeDown()
|
||||||
} else {
|
} else {
|
||||||
self.onSwipeUp()
|
self.onSwipeUp()
|
||||||
|
|
|
@ -12,41 +12,43 @@ public struct GameBoard {
|
||||||
|
|
||||||
public let width: Int
|
public let width: Int
|
||||||
public let height: Int
|
public let height: Int
|
||||||
public internal(set) var tiles: [[Bool]]
|
public internal(set) var tiles: [[Tetromino?]]
|
||||||
|
|
||||||
public init(width: Int, height: Int) {
|
public init(width: Int, height: Int) {
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
tiles = (1...height).map { _ in
|
tiles = (1...height).map { _ in
|
||||||
Array(repeating: false, count: width)
|
Array(repeating: nil, count: width)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public subscript(x: Int, y: Int) -> Bool {
|
public subscript(x: Int, y: Int) -> Bool {
|
||||||
|
return self.tiles[y][x] != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscript(x: Int, y: Int) -> Tetromino? {
|
||||||
return self.tiles[y][x]
|
return self.tiles[y][x]
|
||||||
}
|
}
|
||||||
|
|
||||||
mutating func set(piece: GamePiece) {
|
public mutating func set(piece: GamePiece) {
|
||||||
let (left, top) = piece.topLeft
|
let (left, top) = piece.topLeft
|
||||||
|
|
||||||
for y in 0..<piece.tiles.count where y + top < height {
|
for y in 0..<piece.tiles.count where y + top < height {
|
||||||
for x in 0..<piece.tiles.first!.count where x + left < width {
|
for x in 0..<piece.tiles.first!.count where x + left < width {
|
||||||
if piece.tiles[y][x] {
|
if piece.tiles[y][x] {
|
||||||
self.tiles[y + top][x + left] = true
|
self.tiles[y + top][x + left] = piece.tetromino
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public mutating func set(tiles: [(Int, Int)]) {
|
mutating func clear(tiles: [(Int, Int)]) {
|
||||||
for (x, y) in tiles {
|
for (x, y) in tiles {
|
||||||
self.tiles[y][x] = true
|
self.tiles[y][x] = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mutating func unset(tiles: [(Int, Int)]) {
|
func rowFull(_ row: Int) -> Bool {
|
||||||
for (x, y) in tiles {
|
return tiles[row].allSatisfy({ $0 != nil })
|
||||||
self.tiles[y][x] = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ public class GameController: ObservableObject {
|
||||||
public let width = 10
|
public let width = 10
|
||||||
public let height = 16
|
public let height = 16
|
||||||
|
|
||||||
|
var state: GameState = .waitingForStart
|
||||||
|
|
||||||
@Published public var board: GameBoard
|
@Published public var board: GameBoard
|
||||||
|
|
||||||
@Published public var currentPiece: GamePiece? {
|
@Published public var currentPiece: GamePiece? {
|
||||||
|
@ -33,6 +35,7 @@ public class GameController: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func start() {
|
public func start() {
|
||||||
|
state = .playing(.normal)
|
||||||
nextPiece()
|
nextPiece()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,24 +48,29 @@ public class GameController: ObservableObject {
|
||||||
self.board.set(piece: currentPiece!)
|
self.board.set(piece: currentPiece!)
|
||||||
clearLines()
|
clearLines()
|
||||||
nextPiece()
|
nextPiece()
|
||||||
|
|
||||||
|
if case .playing(.dropped) = state {
|
||||||
|
state = .playing(.normal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearLines() {
|
func clearLines() {
|
||||||
let fullRow = Array(repeating: true, count: width)
|
|
||||||
var row = height - 1
|
var row = height - 1
|
||||||
while row >= 0 {
|
while row >= 0 {
|
||||||
if board.tiles[row] == fullRow {
|
if board.rowFull(row) {
|
||||||
board.tiles.remove(at: row)
|
board.tiles.remove(at: row)
|
||||||
}
|
}
|
||||||
row -= 1
|
row -= 1
|
||||||
}
|
}
|
||||||
for _ in 0..<height - board.tiles.count {
|
for _ in 0..<height - board.tiles.count {
|
||||||
board.tiles.insert(Array(repeating: false, count: width), at: 0)
|
board.tiles.insert(Array(repeating: nil, count: width), at: 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func step() {
|
public func step() {
|
||||||
guard let currentPiece = currentPiece else { return }
|
guard case .playing(_) = state,
|
||||||
|
let currentPiece = currentPiece else { return }
|
||||||
|
|
||||||
let modifiedPiece = currentPiece.moved(by: (0, 1))
|
let modifiedPiece = currentPiece.moved(by: (0, 1))
|
||||||
if overlapsAny(modifiedPiece) {
|
if overlapsAny(modifiedPiece) {
|
||||||
finalizePiece()
|
finalizePiece()
|
||||||
|
@ -72,7 +80,9 @@ public class GameController: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func rotate(direction: RotationDirection) {
|
public func rotate(direction: RotationDirection) {
|
||||||
guard let currentPiece = currentPiece else { return }
|
guard case .playing(_) = state,
|
||||||
|
let currentPiece = currentPiece else { return }
|
||||||
|
|
||||||
let modifiedPiece = currentPiece.rotated(direction)
|
let modifiedPiece = currentPiece.rotated(direction)
|
||||||
if !overlapsAny(modifiedPiece) {
|
if !overlapsAny(modifiedPiece) {
|
||||||
self.currentPiece = modifiedPiece
|
self.currentPiece = modifiedPiece
|
||||||
|
@ -80,7 +90,8 @@ public class GameController: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func left() {
|
public func left() {
|
||||||
guard let currentPiece = currentPiece else { return }
|
guard case .playing(.normal) = state,
|
||||||
|
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) {
|
||||||
self.currentPiece = modifiedPiece
|
self.currentPiece = modifiedPiece
|
||||||
|
@ -88,7 +99,8 @@ public class GameController: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func right() {
|
public func right() {
|
||||||
guard let currentPiece = currentPiece else { return }
|
guard case .playing(.normal) = state,
|
||||||
|
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) {
|
||||||
self.currentPiece = modifiedPiece
|
self.currentPiece = modifiedPiece
|
||||||
|
@ -96,10 +108,12 @@ public class GameController: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func drop() {
|
public func drop() {
|
||||||
|
guard case .playing(.normal) = state else { return }
|
||||||
currentPiece = currentPieceAtDropPoint
|
currentPiece = currentPieceAtDropPoint
|
||||||
|
state = .playing(.dropped)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateCurrentPieceAtDropPoint() {
|
private func updateCurrentPieceAtDropPoint() {
|
||||||
guard let currentPiece = currentPiece else { return }
|
guard let currentPiece = currentPiece else { return }
|
||||||
var prev = currentPiece
|
var prev = currentPiece
|
||||||
currentPieceAtDropPoint = currentPiece
|
currentPieceAtDropPoint = currentPiece
|
||||||
|
@ -123,3 +137,13 @@ public class GameController: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum GameState {
|
||||||
|
case waitingForStart
|
||||||
|
case playing(PlayState)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PlayState {
|
||||||
|
case normal
|
||||||
|
case dropped
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Foundation
|
||||||
|
|
||||||
public struct GamePiece {
|
public struct GamePiece {
|
||||||
public let tetromino: Tetromino
|
public let tetromino: Tetromino
|
||||||
public internal(set) 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) {
|
||||||
|
|
|
@ -18,11 +18,21 @@ struct CurrentPieceView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GridView(rows: self.boardHeight, columns: self.boardWidth) { (col, row, size) in
|
GridView(rows: self.boardHeight, columns: self.boardWidth) { (col, row, size) in
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.foregroundColor(self.currentPieceAt(col, row) ? Color.blue : self.droppedPieceAt(col, row) ? Color.gray : Color.clear)
|
.foregroundColor(self.colorAt(col, row))
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func colorAt(_ col: Int, _ row: Int) -> Color {
|
||||||
|
if currentPieceAt(col, row) {
|
||||||
|
return self.currentPiece!.tetromino.color
|
||||||
|
} else if droppedPieceAt(col, row) {
|
||||||
|
return .gray
|
||||||
|
} else {
|
||||||
|
return .clear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func currentPieceAt(_ col: Int, _ row: Int) -> Bool {
|
func currentPieceAt(_ col: Int, _ row: Int) -> Bool {
|
||||||
guard let currentPiece = self.currentPiece else { return false }
|
guard let currentPiece = self.currentPiece else { return false }
|
||||||
let (left, top) = currentPiece.topLeft
|
let (left, top) = currentPiece.topLeft
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// Tetromino+Color.swift
|
||||||
|
// Tetris
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 10/15/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import TetrisKit
|
||||||
|
|
||||||
|
fileprivate extension Color {
|
||||||
|
static let tetrominoAqua = Color(red: 0, green: 214/255, blue: 211/255)
|
||||||
|
static let tetrominoBlue = Color(red: 0, green: 79/255, blue: 214/255)
|
||||||
|
static let tetrominoOrange = Color(red: 255/255, green: 128/255, blue: 0)
|
||||||
|
static let tetrominoYellow = Color(red: 255/255, green: 229/255, blue: 0)
|
||||||
|
static let tetrominoGreen = Color(red: 0, green: 214/255, blue: 71/255)
|
||||||
|
static let tetrominoPurple = Color(red: 168/255, green: 0, blue: 214/255)
|
||||||
|
static let tetrominoRed = Color(red: 214/255, green: 0, blue: 61/255)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Tetromino {
|
||||||
|
var color: Color {
|
||||||
|
switch self {
|
||||||
|
case .i:
|
||||||
|
return .tetrominoAqua
|
||||||
|
case .o:
|
||||||
|
return .tetrominoYellow
|
||||||
|
case .t:
|
||||||
|
return .tetrominoPurple
|
||||||
|
case .j:
|
||||||
|
return .tetrominoOrange
|
||||||
|
case .l:
|
||||||
|
return .tetrominoBlue
|
||||||
|
case .s:
|
||||||
|
return .tetrominoRed
|
||||||
|
case .z:
|
||||||
|
return .tetrominoGreen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ struct TilesView: View {
|
||||||
GridView(rows: board.height, columns: board.width) { (col, row, size) in
|
GridView(rows: board.height, columns: board.width) { (col, row, size) in
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.foregroundColor(self.board[col, row] ? Color.red : Color.black)
|
.foregroundColor(self.board[col, row]?.color ?? Color.clear)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,9 @@ struct TilesView: View {
|
||||||
struct TilesView_Previews: PreviewProvider {
|
struct TilesView_Previews: PreviewProvider {
|
||||||
@State static var board: GameBoard = {
|
@State static var board: GameBoard = {
|
||||||
var b = GameBoard(width: 10, height: 16)
|
var b = GameBoard(width: 10, height: 16)
|
||||||
b.set(tiles: [(0, 15), (1, 15), (1, 14), (2, 14)])
|
var piece = GamePiece(tetromino: .z)
|
||||||
|
piece.topLeft = (0, 14)
|
||||||
|
b.set(piece: piece)
|
||||||
return b
|
return b
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ extension TwoDimString {
|
||||||
setColumn(controller.width + 1, to: "+" + String(repeating: "|", count: controller.height) + "+")
|
setColumn(controller.width + 1, to: "+" + String(repeating: "|", count: controller.height) + "+")
|
||||||
|
|
||||||
for y in 0..<controller.height {
|
for y in 0..<controller.height {
|
||||||
for x in 0..<controller.width where controller.board.get(tile: (x, y)) {
|
for x in 0..<controller.width where controller.board[x, y] {
|
||||||
self[x + 1, y + 1] = "X"
|
self[x + 1, y + 1] = "X"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ extension TwoDimString {
|
||||||
}
|
}
|
||||||
|
|
||||||
let controller = GameController()
|
let controller = GameController()
|
||||||
controller.currentPiece = GamePiece(tetromino: .l)
|
controller.start()
|
||||||
|
|
||||||
func readMove() {
|
func readMove() {
|
||||||
print(TwoDimString(controller: controller))
|
print(TwoDimString(controller: controller))
|
||||||
|
|
Loading…
Reference in New Issue