Skip to content

Commit

Permalink
Merge pull request #1 from g-liu/the-keyboard
Browse files Browse the repository at this point in the history
the keyboard
  • Loading branch information
g-liu authored Mar 4, 2022
2 parents 8897b0a + 9d9a382 commit 53d12f9
Show file tree
Hide file tree
Showing 16 changed files with 546 additions and 69 deletions.
44 changes: 40 additions & 4 deletions WordleWithFriends.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
6C19F5A927921EFB00062083 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6C19F5A727921EFB00062083 /* LaunchScreen.storyboard */; };
6C19F5BF27921EFC00062083 /* WordleWithFriendsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C19F5BE27921EFC00062083 /* WordleWithFriendsUITests.swift */; };
6C19F5D127922D6700062083 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C19F5D027922D6700062083 /* String+Extension.swift */; };
6C19F5D62792356500062083 /* WordGuessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C19F5D52792356500062083 /* WordGuessViewController.swift */; };
6C19F5D62792356500062083 /* ClueGuessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C19F5D52792356500062083 /* ClueGuessViewController.swift */; };
6C19F5E527923B1400062083 /* WordGuessRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C19F5E427923B1400062083 /* WordGuessRow.swift */; };
6C19F5F027923C5900062083 /* NSLayoutConstraint+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C19F5EF27923C5900062083 /* NSLayoutConstraint+Extension.swift */; };
6C19F5FD27923CBB00062083 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C19F5FC27923CBB00062083 /* UIView+Extension.swift */; };
Expand All @@ -54,6 +54,12 @@
6C78E0C12797902700C501CD /* XCUIElement+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C78E0C02797902700C501CD /* XCUIElement+Extension.swift */; };
6C834C8E279C87160024CB13 /* GameUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C834C8D279C87160024CB13 /* GameUtility.swift */; };
6C834C90279C897F0024CB13 /* IndexPath+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C834C8F279C897F0024CB13 /* IndexPath+Extension.swift */; };
6C94887127D07FD4005252F1 /* WordleKeyboardInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C94887027D07FD4005252F1 /* WordleKeyboardInputView.swift */; };
6C94887327D08598005252F1 /* WordleKeyboardKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C94887227D08598005252F1 /* WordleKeyboardKey.swift */; };
6C94887627D098C7005252F1 /* WeakRef.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C94887527D098C7005252F1 /* WeakRef.swift */; };
6C94887827D0AAD9005252F1 /* LetterState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C94887727D0AAD9005252F1 /* LetterState.swift */; };
6C94887C27D0AB15005252F1 /* LetterStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C94887B27D0AB15005252F1 /* LetterStateTests.swift */; };
6C94887E27D0B4E4005252F1 /* KeyboardRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C94887D27D0B4E4005252F1 /* KeyboardRow.swift */; };
6CBBCECF279BDC7D00875C30 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CBBCECE279BDC7D00875C30 /* Double+Extension.swift */; };
6CBCC3CD2797565D005EB254 /* DismissableAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CBCC3CC2797565D005EB254 /* DismissableAlertController.swift */; };
6CBCC3D1279764FF005EB254 /* CGPoint+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CBCC3D0279764FF005EB254 /* CGPoint+Extension.swift */; };
Expand Down Expand Up @@ -119,7 +125,7 @@
6C19F5BE27921EFC00062083 /* WordleWithFriendsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordleWithFriendsUITests.swift; sourceTree = "<group>"; };
6C19F5C027921EFC00062083 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
6C19F5D027922D6700062083 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = "<group>"; };
6C19F5D52792356500062083 /* WordGuessViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordGuessViewController.swift; sourceTree = "<group>"; };
6C19F5D52792356500062083 /* ClueGuessViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClueGuessViewController.swift; sourceTree = "<group>"; };
6C19F5E427923B1400062083 /* WordGuessRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordGuessRow.swift; sourceTree = "<group>"; };
6C19F5EF27923C5900062083 /* NSLayoutConstraint+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLayoutConstraint+Extension.swift"; sourceTree = "<group>"; };
6C19F5FC27923CBB00062083 /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = "<group>"; };
Expand All @@ -132,6 +138,12 @@
6C78E0C02797902700C501CD /* XCUIElement+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extension.swift"; sourceTree = "<group>"; };
6C834C8D279C87160024CB13 /* GameUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameUtility.swift; sourceTree = "<group>"; };
6C834C8F279C897F0024CB13 /* IndexPath+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IndexPath+Extension.swift"; sourceTree = "<group>"; };
6C94887027D07FD4005252F1 /* WordleKeyboardInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordleKeyboardInputView.swift; sourceTree = "<group>"; };
6C94887227D08598005252F1 /* WordleKeyboardKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordleKeyboardKey.swift; sourceTree = "<group>"; };
6C94887527D098C7005252F1 /* WeakRef.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakRef.swift; sourceTree = "<group>"; };
6C94887727D0AAD9005252F1 /* LetterState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterState.swift; sourceTree = "<group>"; };
6C94887B27D0AB15005252F1 /* LetterStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterStateTests.swift; sourceTree = "<group>"; };
6C94887D27D0B4E4005252F1 /* KeyboardRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardRow.swift; sourceTree = "<group>"; };
6CBBCECE279BDC7D00875C30 /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = "<group>"; };
6CBCC3CC2797565D005EB254 /* DismissableAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissableAlertController.swift; sourceTree = "<group>"; };
6CBCC3D0279764FF005EB254 /* CGPoint+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGPoint+Extension.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -177,6 +189,7 @@
children = (
60D948032793A72800086C51 /* LayoutUtility.swift */,
6C834C8D279C87160024CB13 /* GameUtility.swift */,
6C94887527D098C7005252F1 /* WeakRef.swift */,
);
path = Utils;
sourceTree = "<group>";
Expand Down Expand Up @@ -272,7 +285,7 @@
6C19F59E27921EFA00062083 /* SceneDelegate.swift */,
6C19F5A027921EFA00062083 /* GameSetupViewController.swift */,
60D948072793BB0D00086C51 /* Settings */,
6C19F5D52792356500062083 /* WordGuessViewController.swift */,
6C19F5D52792356500062083 /* ClueGuessViewController.swift */,
6C19F5A527921EFB00062083 /* Assets.xcassets */,
6C19F5A727921EFB00062083 /* LaunchScreen.storyboard */,
6C19F5AA27921EFC00062083 /* Info.plist */,
Expand Down Expand Up @@ -319,6 +332,7 @@
6C19F5E327923B0B00062083 /* Views */ = {
isa = PBXGroup;
children = (
6C94887427D08C64005252F1 /* CustomKeyboard */,
6C19F5E427923B1400062083 /* WordGuessRow.swift */,
6C19F60127923F8400062083 /* LetterTileView.swift */,
6C0009E12792AE8200BCC9B6 /* WordInputTextField.swift */,
Expand All @@ -333,6 +347,7 @@
6C39559E279288D100B0EF29 /* WordGuess.swift */,
6C3955A0279288E800B0EF29 /* LetterGuess.swift */,
60D948002793A46700086C51 /* GameEndDelegate.swift */,
6C94887727D0AAD9005252F1 /* LetterState.swift */,
);
path = Models;
sourceTree = "<group>";
Expand All @@ -343,6 +358,7 @@
6C3955A22792890F00B0EF29 /* WordGuessTests.swift */,
60D947F927938A6900086C51 /* LetterGuessTests.swift */,
60D947FB27938AD200086C51 /* GameGuessesModelTests.swift */,
6C94887B27D0AB15005252F1 /* LetterStateTests.swift */,
);
path = Models;
sourceTree = "<group>";
Expand All @@ -355,6 +371,16 @@
path = Extensions;
sourceTree = "<group>";
};
6C94887427D08C64005252F1 /* CustomKeyboard */ = {
isa = PBXGroup;
children = (
6C94887027D07FD4005252F1 /* WordleKeyboardInputView.swift */,
6C94887227D08598005252F1 /* WordleKeyboardKey.swift */,
6C94887D27D0B4E4005252F1 /* KeyboardRow.swift */,
);
path = CustomKeyboard;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -425,6 +451,7 @@
};
6C19F5AE27921EFC00062083 = {
CreatedOnToolsVersion = 12.2;
LastSwiftMigration = 1320;
TestTargetID = 6C19F59827921EFA00062083;
};
6C19F5B927921EFC00062083 = {
Expand Down Expand Up @@ -498,7 +525,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6C19F5D62792356500062083 /* WordGuessViewController.swift in Sources */,
6C19F5D62792356500062083 /* ClueGuessViewController.swift in Sources */,
6C19F60B2792439600062083 /* GameGuessesModel.swift in Sources */,
60D948102793E2BC00086C51 /* GameSettingTableViewCell.swift in Sources */,
6C3955A1279288E800B0EF29 /* LetterGuess.swift in Sources */,
Expand All @@ -514,15 +541,20 @@
60D948092793BD0300086C51 /* GameSettings.swift in Sources */,
6CBCC3D32797657B005EB254 /* CGSize+Extension.swift in Sources */,
6C19F5E527923B1400062083 /* WordGuessRow.swift in Sources */,
6C94887127D07FD4005252F1 /* WordleKeyboardInputView.swift in Sources */,
6CBBCECF279BDC7D00875C30 /* Double+Extension.swift in Sources */,
6C19F60227923F8400062083 /* LetterTileView.swift in Sources */,
6CBCC3CD2797565D005EB254 /* DismissableAlertController.swift in Sources */,
60D948062793AE1400086C51 /* GameSettingsViewController.swift in Sources */,
6C94887827D0AAD9005252F1 /* LetterState.swift in Sources */,
6C834C8E279C87160024CB13 /* GameUtility.swift in Sources */,
60D948142793E50100086C51 /* GameSettingProtocol.swift in Sources */,
6C0009E22792AE8200BCC9B6 /* WordInputTextField.swift in Sources */,
6C19F59F27921EFA00062083 /* SceneDelegate.swift in Sources */,
6C94887327D08598005252F1 /* WordleKeyboardKey.swift in Sources */,
6C94887627D098C7005252F1 /* WeakRef.swift in Sources */,
60D948012793A46700086C51 /* GameEndDelegate.swift in Sources */,
6C94887E27D0B4E4005252F1 /* KeyboardRow.swift in Sources */,
6CBCC3D1279764FF005EB254 /* CGPoint+Extension.swift in Sources */,
6C19F6162792491900062083 /* UIStackView+Extension.swift in Sources */,
60D947FF2793A23700086C51 /* GameMessagingViewController.swift in Sources */,
Expand All @@ -536,6 +568,7 @@
buildActionMask = 2147483647;
files = (
6C3955A32792890F00B0EF29 /* WordGuessTests.swift in Sources */,
6C94887C27D0AB15005252F1 /* LetterStateTests.swift in Sources */,
60D947FA27938A6900086C51 /* LetterGuessTests.swift in Sources */,
60D947FC27938AD200086C51 /* GameGuessesModelTests.swift in Sources */,
);
Expand Down Expand Up @@ -736,6 +769,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 2A23N78DMF;
INFOPLIST_FILE = WordleWithFriendsTests/Info.plist;
Expand All @@ -747,6 +781,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = letsgooo.WordleWithFriendsTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WordleWithFriends.app/WordleWithFriends";
Expand All @@ -758,6 +793,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 2A23N78DMF;
INFOPLIST_FILE = WordleWithFriendsTests/Info.plist;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
//
// WordGuessViewController.swift
// ClueGuessViewController.swift
// WordleWithFriends
//
// Created by Geoffrey Liu on 1/14/22.
//

import UIKit
import AudioToolbox

final class WordGuessViewController: UIViewController {
final class ClueGuessViewController: UIViewController {

// Extremely hacky workaround of table jumpiness when reloading...
// https://stackoverflow.com/questions/28244475/reloaddata-of-uitableview-with-dynamic-cell-heights-causes-jumpy-scrolling
Expand All @@ -34,6 +35,12 @@ final class WordGuessViewController: UIViewController {
return tableView
}()

private lazy var wordleKeyboard: WordleKeyboardInputView = {
let inputView = WordleKeyboardInputView()
inputView.delegate = self
return inputView
}()

private lazy var guessInputTextField: UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false
Expand All @@ -44,6 +51,7 @@ final class WordGuessViewController: UIViewController {
textField.delegate = self
textField.layer.borderWidth = 1
textField.layer.borderColor = UIColor.darkText.cgColor
textField.inputView = wordleKeyboard

return textField
}()
Expand Down Expand Up @@ -90,7 +98,7 @@ final class WordGuessViewController: UIViewController {
loadingView.pin(to: view.safeAreaLayoutGuide)

guessInputTextField.becomeFirstResponder()
title = "Guess the word"
title = "Guess the clue"

navigationItem.rightBarButtonItem = shareButton
}
Expand Down Expand Up @@ -140,8 +148,25 @@ final class WordGuessViewController: UIViewController {
}

private func submitGuess() {
// TODO: Move some checks to view model???
guard let wordGuess = guessInputTextField.text,
wordGuess.count == GameSettings.clueLength.readIntValue(),
GameSettings.allowNonDictionaryGuesses.readBoolValue() || wordGuess.isARealWord() else {
gameGuessesModel.markInvalidGuess()
let currentIndexPath = IndexPath.Row(gameGuessesModel.numberOfGuesses)
guessTable.reloadRows(at: [currentIndexPath], with: .none)

AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))

return
}

let gameState = gameGuessesModel.submitGuess()

if let mostRecentGuess = gameGuessesModel.mostRecentGuess {
wordleKeyboard.updateState(with: mostRecentGuess)
}

guessTable.reloadData()

guessInputTextField.text = ""
Expand Down Expand Up @@ -189,7 +214,7 @@ final class WordGuessViewController: UIViewController {
}
}

extension WordGuessViewController: UITableViewDelegate, UITableViewDataSource {
extension ClueGuessViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
1
}
Expand Down Expand Up @@ -253,18 +278,8 @@ extension WordGuessViewController: UITableViewDelegate, UITableViewDataSource {
}
}

extension WordGuessViewController: UITextFieldDelegate {
extension ClueGuessViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// this is how we submit a guess
guard let wordGuess = textField.text,
wordGuess.count == GameSettings.clueLength.readIntValue(),
GameSettings.allowNonDictionaryGuesses.readBoolValue() || wordGuess.isARealWord() else {
gameGuessesModel.markInvalidGuess()
let currentIndexPath = IndexPath.Row(gameGuessesModel.numberOfGuesses)
guessTable.reloadRows(at: [currentIndexPath], with: .none)
return false
}

submitGuess()
return false
}
Expand All @@ -276,26 +291,22 @@ extension WordGuessViewController: UITextFieldDelegate {
return false
}

// Note: We still need this function as users can use bluetooth keyboard etc. to bypass the onscreen input
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard !gameGuessesModel.isGameOver else {
return false
}

guard string.isLettersOnly() else {
return false
}

guard (textField.text?.count ?? 0) + string.count <= GameSettings.clueLength.readIntValue() else {
return false
}
guard !gameGuessesModel.isGameOver,
string.isLettersOnly(),
(textField.text?.count ?? 0) + string.count <= GameSettings.clueLength.readIntValue() else {
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
return false
}

gameGuessesModel.clearInvalidGuess()

return true
}
}

extension WordGuessViewController: GameEndDelegate {
extension ClueGuessViewController: GameEndDelegate {
func shareResult() {
shareAction(nil)
}
Expand All @@ -305,13 +316,36 @@ extension WordGuessViewController: GameEndDelegate {
}

func restartWithNewClue() {
let newClue = GameUtility.pickWord(length: GameSettings.clueLength.readIntValue())
let newClue = GameUtility.pickWord()
gameGuessesModel = GameGuessesModel(clue: newClue)

wordleKeyboard.resetKeyboard()

DispatchQueue.main.async { [weak self] in
self?.guessTable.reloadData()
// TODO: In the future might have to reset `cellHeightCache`
self?.guessTable.scrollToRow(at: .zero, at: .bottom, animated: true)
}
}
}

extension ClueGuessViewController: KeyTapDelegate {
func didTapKey(_ char: Character) {
guard !gameGuessesModel.isGameOver,
guessInputTextField.text?.isLettersOnly() ?? false,
(guessInputTextField.text?.count ?? 0) < GameSettings.clueLength.readIntValue() else {
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
return
}

guessInputTextField.insertText("\(char)")
}

func didTapSubmit() {
submitGuess()
}

func didTapDelete() {
guessInputTextField.deleteBackward()
}
}
11 changes: 8 additions & 3 deletions WordleWithFriends/GameSetupViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,20 +167,25 @@ final class GameSetupViewController: UIViewController {
}

@objc private func initiateGameWithRandomWord() {
clueTextField.text = GameUtility.pickWord(length: GameSettings.clueLength.readIntValue())
var clue = ""
repeat {
clue = GameUtility.pickWord()
} while !clue.isARealWord()

clueTextField.text = clue

initiateGame(.computer)
}

private func initiateGame(_ clueSource: ClueSource) {
// start game
let wordGuessVC = WordGuessViewController(clue: clueTextField.text?.uppercased() ?? "", clueSource: clueSource)
let clueGuessVC = ClueGuessViewController(clue: clueTextField.text?.uppercased() ?? "", clueSource: clueSource)
clueTextField.text = ""
startGameButton.isEnabled = false

clueTextField.resignFirstResponder()

navigationController?.pushViewController(wordGuessVC, animated: true)
navigationController?.pushViewController(clueGuessVC, animated: true)
}
}

Expand Down
Loading

0 comments on commit 53d12f9

Please sign in to comment.