Pitch
AthLink bridges the gap between your passion for sports and the challenge of finding like-minded athletes to play with in person. In existing apps, there are ways to connect and meet new people, but when it comes to finding other athletes to play with, it is hard to know if you will be compatible playing with another athlete without knowing their skill level and their goals for practice. This app will keep track of all of that information for you, and only suggest athletes that are compatible with what you are looking for in an athlete to play with. In addition, you can track your progress by inputting your workouts and setting weekly goals. Add pictures and videos to your profile to show other users your skills and highlights! Join us in revolutionizing the way athletes connect, compete, and build communities.
Functional Design
User
Purpose:
User authentication-- allows user to have an individualized identity
Principle:
After a user registers with a username, password, they can identify as that user by inputting the same username and password.
States:
Registered: set User
username,password: registered => one String
Actions:
register(username:String, password:String,basic_data:set out user: User)
user not in registered
registered+=user
u.username:=username
u.password:=passwordauthenticate(username:String,password:String, out u:User)
user in registered
user.username=username and user.password=password
Profile[Post,User,SkillScore]
Purpose:
holds all of the user’s stats, pictures, and background information
Principle:
After a user registers, they can add their estimated skill score, goals for playing, and background info (gender, sport, etc.) to their profile
States:
Current_posts: Profile=>set Post
profile_info: Profile=>set(Dict('gender':one String,sports: set String, skill: SkillScore, location: String,goal:String))
Actions:
updateInfo(profile_info,current_posts): update the user’s profile information or add a stat (with a picture) getInfo(profile_info): get the User’s information deleteProfile(current_posts): temporarily hide all posts on the profile from all other users revealProfile(current_posts): if profile was deleted, user can restore it
Alike[User,SkillScore]
Purpose:
shows you the users that match your preference
Principle:
when user inputs their preferences, this filters out the athletes that don’t match those references, so the user only sees athletes that match those preferences
States:
preferences: Alike => set(Dict('location_range': Number, 'gender':one String, 'goal':one String, 'sport':set String,'skill_range': (SkillScore,SkillScore)))
recommended_users: Alike => set User
Actions:
- updatePreferences(preferences): user can update their preferences to re-filter for other athletes
- filter(preferences,recommended_users): uses your preferences to filter all users and show only valid users (users that meet all preferences)
- showUsers(recommended_users): shows you all the users in the valid users set (all users that meet your preferences)
Post[Profile]
Purpose:
Share a stat, picture, or video to your profile/feed
Principle:
When a user finishes a match, they can post their stats and a picture of them playing
States:
Stat: Post => one Stat
Picture: Post => one Picture
comment: Post => set Comment
postDate: Post => one date
Actions:
- editPost(stat,picture,comment): Edit any information on the post
- deletePost(stat,picture): Delete the post; results in post being deleted on your profile and other people’s feeds
- addPost(Stat,Picture,postDate): Add a post; results in post being added to your profile and other people’s feeds (if they are in the Group of users that have visibility access)
Visibility[Post,User]
Purpose:
Make post visible to selected group of users
Principle:
When a user posts content, they can choose to post it to the public (viewable by any user), private (only author can view), or friend(viewable by all friends of the author) group
States:
status: Visibility=> {Private | Friends | Public}
Friends: Visibility => set User post: Visibility => one Post userToAdd: Visibility => one User userToRemove:Visibility => one User
Actions:
addFriend(Friends,userToAdd): add Friend to friend group removeFriend(Friends,userToRemove): remove Friend from friend group setAccess(post,friends,status) :set which users can see a given post
Connect[Profile]:
Purpose:
Enables athletes to talk with each other so they can set up a time to play.
Principle:
When a user wants to play or connect with another athlete, they send a request to play. If the request is accepted by the athlete, they can keep talking and are now friends.
States:
from,to: Connect => one User
user1Group: Connect => Profile.profile_info.Friends user2Group: Connect => Profile.profile_info.Friends
Actions:
sendRequest(from,to) //sends a request from one user to another to continue talking acceptRequest(user1Group,user2Group) //once accepted, both users will be added to the other user’s "friends" group denyRequest(from,to) //prevents users from further talking with each other
SkillScore[Post]:
Purpose:
categorize users based on their results
Principle:
If you beat a user with a higher skill score, this is considered positive for you but negative for them, so your skill score increases and theirs decreases.
States:
currentScore: SkillScore=> one Number
opponentScore:SkillScore=> one Number Score: SkillScore => (Post.Stat, otherScore) :Post.Stat is the match score, otherScore is the opponents skillScore
valid_scores: SkillScore => set Scores
Actions:
addScore(currentScore,scores,opponentScore,valid_scores): for every match that is played and for every piece of info that is inputted regarding user’s score, the user’s score will be re-evaluated and updated removeScore(currentScore,scores,valid_scores): removes the Stat
ExpiringResource[Post]
NOTE: got this idea from the tutorial (so pretty much reusing a concept)
Purpose:
set a "life-span" for scores
Operational Principle:
After user inputs a Stat (by posting it), that stat is given a life span of one year. After the life span is over, the stat will not be considered in the overall calculation of the users SkillScore.
States:
active: set Post.Stat
expiry: Post -> one Date
Actions:
allocate (r: Post, t: int) r.Stat not in active active += r r.expiry := // t secs after now deallocate (r: Post.Stat) r.Stat in active active -= r r.expiry := none renew (r: Post.Stat, t: int) r.Stat in active r.expiry := // t secs after now system expire (out r: Post.Stat) r.Stat in active r.expiry is before now active -= r r.expiry := none
Concept Syncs
app Athlink:
include User
include Profile
include Post
include Visibility
include SkillScore
include ExpiringResource
include Alike
include Connect
sync register(username:String,password:String,out user:User,profile_info)
When User.register(username,password)
Profile.updateInfo(profile_info,current_posts)
sync accept(user1Group:set User, user2Group:set User,from,to:User)
When Connect.accept(from,to,user1Group,user2Group)
Visibility.addFriends(user1Group,to) && Visibility.addFriends(user2Group,from) //add users to each other's to Friends group
sync deleteContent(item,current_posts:set Post)
When Post.deletePost(item)
Profile.updateInfo(profile_info,current_posts) //delete post from current_posts
sync addContent(Stat,current_posts:set Post,valid_scores:set Scores,current_score: SkillScore)
When Post.addPost(Stat,Picture)
Profile.updateInfo(profile_info,current_posts)
Visibility.setAccess(author,friends,status)
SkillScore.addScore(currentScore,scores,opponentScore,valid_scores) //update SkillScore
sync expire(post:one Post,current_scores,score)
when ExpiringResource.expire(Post)
SkillScore.removeScore(current_scores,score,valid_scores)
sync removeScore(score:SkillScore=> (Post.stat,Number),currentScore:SkillScore,valid_scores set Scores,profile_info,current_posts: set Post)
when SkillScore.removeScore(currentScore,score,valid_scores)
Profile.update_info(profile_info,current_posts) //update SkillScore on Profile
sync preferences(profile_info,current_posts:set Post,preferences,recommended_users:set User)
when Profile.update_info(profile_info,current_posts) //ex:if you change location, the set of users that we are filtering for is now different
Alike.filter(preferences,recommended_users)
Wireframes
Click on the link below to view the wireframes:
Design Tradeoffs
An Expiring SkillScore
(b). I had to decide whether a user's SkillScore should be based on all of their uploaded stats, no matter how long ago they were uploaded, or if I should set a time limit on a stat that is uploaded.
(c). I decided that a user's SkillScore should only be based on stats that were uploaded within the last year. This is because a user may have been good years ago, but if they stopped playing for a while, they are probably not still at that skill level. Likewise, a user may have improved a bunch, and their old stats are no longer reflective of the player they now are.
Allowing Videos
(b). I had to decide whether a user should be able to upload videos when they post their stats, or whether it should just be pictures.
(c). While videos are a fun way to showcase highlights and good plays/points, I decided not to allow videos, because it could potentially misportray a user's skill level. The SkillScore is designed to accurately calculate the skill level of a player, but a player may look much better than they really are if they only upload videos of their highlights. The benefits of videos are they are a fun way for a user to show their skills, but in my opinion, the cons of misportraying skill level far outweighs this benefit.
Info Portrayed
(b). I had to decide whether to portray all of the user's profile information (gender,SkillScore,sports,location,Goal) on their profile. (c). Since the profile is accessable by any user (even if not all the posts on the profile are accessable), I decided it wouldn't be safe for the location of the user to be displayed on the user's profile. I decided it was important to keep the rest of the information, because that information is important in considering how compatible two athletes may be.