Please go to the to-publish branch for newest changes made to the project. We will not touch main for now to avoid interfering with the original version.
Go to 'Issues' to find some tasks to work on. Will be updated with more as we progress.
In the multiple rounds version of this game, we are manipulating the shared state with only one state object rather than two (as in the one round case). This object is the hostState, which only the host can manipulate.
In the case of shared state changed by host:
- Host performs changes locally.
- Host calls sync to deliver the updated state to all active players.
In the case of shared state changed by players:
- Player uses pool.send(message, host), where message is one of the structs that implement the Message protocol.
- That message contains instructions for the host to follow.
- Host propagates those changes towards all players.
The project is broken down into stages. These stages are:
- Standalone UI / Screens
- We first create standalone screens but don't connect them to other screen or any external state. This will allow us to take care of UI first and then tie it all together while making sure we can test the app going forward.
- Connectivity API:
- We need a way to talk to other devices. We've explored the connectivity API but we want something more high-level that will allow us to send arbitrary message to other clients.
- This means creating a wrapper library for the connectivity API
- Once we have that, we can send discrete messages between clients, which will allow us to synchronize the game state between multiple clients
- Putting it all together.
- Once we have the state synchronized, we can connect the state to the UI we created in stage 1
- This means creating a game state object, passing it in the view hierarchy, and reading the appropriate data.
- This also means that any action that modifies the state will also have to send a message to the correct devices, so they can update their state and present the correct data
- Finally, the order of the screen is also a piece of state. This means that at this stage we will use
NavigationStack
that will allow us to present the appropriate screen to the user and finally tie all the UI/Screens together
Due next Thursday meeting April 6th
Based on this Figma
Each of you will be assigned a screen to implement.
If you encounter any issues, please ask for help.
For now, we will create screens with stub data. For example, if the Figma design contains a button, you will use Button
with a stub title and empty action. Later, in stage 3, we will replace these stubs with actual data. For now, Button("Press me") { }
will do just fine.
Please, try to build screens as close as possible to the design in Figma, but don't spend too much time, especially right now that the design is not finalized, getting it pixel-perfect. As long as buttons work and text is in the right place, it should suffice.
You will find screen names at the top left corner of the screen thumbnail in Figma. Then, look up your screen assignment below, based on your github username, and start creating 👷♂️🧑💻✨🤤
Landing -> laurennpak
Join Session -> nac5504
Create Session -> mko02
Session Waiting -> sts04038
Player Order -> noahsadir
Image Creator -> matheweon
Waiting for Image -> ghmcguire
Waiting for Guesses -> TBD
Guessing Image -> TBD
Ranking Guesses -> Aziz
Waiting for Ranking -> Aziz
Ranking Results -> Aziz
We made a wrapper around the connectivity API that makes it easier (hopefully) to work with the API. There is a MultipeerManager
that handles advertising and browsing, and there is a MessagePool
that you can use to send messages to the host and broadcast messages from the host to all the player. Specifically,
MultipeerManager
:
startAdvertising()
starts MC advertising, this is called by the players ononAppear
startBrowsing()
starts MC browsing for nearby peers, called by the game host (GH).stopAdvertising()
/stopBrowsing()
stop the respective activities, called fromonDisappear
In the current version, the GH will be continuously looking for peers and automatically connect to them. The players that get an invitation from a host would automatically accept. So,
-
GH → Player (invitation)
-
Player → GH (accept invitation) Once the player is connected, it gets a list of connected GHs and it displays it in the
JoinSession
screen. Upon selecting a GH, the player sends aRequestToPlay
message, using theMessagePool
API. If the GH accepts the request from the player, the player can move on to theSessionWaiting
screen. So, -
Player →(request to play) GH
-
GH →(response) Player
- if accepted: move to the next screen
- go back to the previous screen
Speaking of the MessagPool
, it has two public functions send()
and sync()
. Players must not call sync. The syncing is reserved for the GH to synchronized the shared state. We have a server-client model where the GH is the server and the players are the clients. This means the players cannot directly communicate with other clients, and the only wait to do it is through the GH.
The players can send messages to the GH to alter the shared state or have the host do something. For that, you need to define a new message type, conforming to the Message
protocol, as well as the message type in the MessageType
enum. Do not forget to make a new entry to the typeMap
property of the MessageType
. That property is essential to decoding and properly handling the messages.
Now is the time to put the screens together. We are not entirely sure how to divide the the tasks, so I think we are going to just code and see what happens. But if I were to break it down by screen, it would look something like this:
- Create Game: → Create Session
- Join Game: → Join Session
- {SM} Request to Join (Game)
- Approve: → Session Waiting
- Deny: UI to reflect denial
- {RM} GameState
- {RM} Game is starting: → PlayerOrder
- {RM} Game has started: → Waiting For Image
- {RM} “Aziz is thinking” → “DALL-E is thinking”
- {RM} Image is created: → Guessing Image
- render the image (use the url from the shared state and
AsyncImage
) - once “Done” is pressed
- send the guess to the host
pool.send(MadeAGuess(guess: "Bear riding a shark"), host: state.host)
2 things:
- Update the state for guesses
- Move the player →
WaitingForRanking
- Once all the guesses have been made, move the host to the
RankingGuesses
struct MadeAGuess: Message {
let guess: String
/// Called on the host!!!
func apply(from peer: Peer, to state: GameStaet) {
state.sync {
// step 2 from above
state.currentScreen[peer] = .waitingForRanking
// step 1 from above
state.guesses[peer] = guess
// step 3
// hint: rememver that the dict will have nil values for everyone
// that hasn't made a guess
if guesses have been made {
state.currenScreen[host] = .ranking
}
}
}
}
- Make API call to DALL-E and spin
- Optionally, change the state from “Aziz is thinking” → “DALL-E is thinking”
- Disable the timer
- Once the API call returns with an image url
- set image url on the shared state
- change current screen for everyone → Guessing Image
- sync
- Rank them
- Once “Done” is pressed
- send the ranking to all the players
- Hint. use the
Shared
struct
- Hint. use the
- Move each player to the
RankingResults
- send the ranking to all the players
- {RM} Guesses ([peer: guess])
- {RM} Produced rankings ([peer: guess]): → Ranking Results
ToDo:
- Use actual name from the Landing Page
-
PlayerOrder
view change the stub names to actual names, kind of depends on #1 (Maksim) -
JoinLocalSession
(Noah)- change the horizontal padding
- change the tint of the selected session
- White with 0.68 opacity
- change the number of players in the session
- remove the cell separators
- UI (spinner) to wait for request to be approved (next to person icon)
- “Join Game” disabled until either rejected or accepted
-
ImageCreator
(Gabe) -
WaitingForGuesses
(Gabe) -
CreateSessionView
(Alena + Maksim)- Dark mode support
- align buttons
- Change description of state enums
- functionality to kick people out of session (bigger button)
- foreground color on text (white)
- segmented control tint color (hack UISegmentedControl.apperarance() )
- Don’t let the game to start unless (John)
- .disabled(true if … )
- There are at least one player
- All the players are in the
Ready
state
- Image Creator View
- Change everything according to Figma
- Merge
WaitingForImage
andWaitingForGuesses
- Bug when all gueses are made, host doesnt move to →
RankingGuesses
(Maks) - Restart the game (Maks)
- Timers (Nick)