From 5733d9fb4712ac96460adc281336cdfdd245e723 Mon Sep 17 00:00:00 2001 From: Bijan Date: Tue, 2 Jan 2018 22:12:23 -0500 Subject: [PATCH 01/23] Update app.js to use consistent js import syntax --- assets/js/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/app.js b/assets/js/app.js index d500c11..5ee11e6 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -21,7 +21,7 @@ import "phoenix_html" // import socket from "./socket" // Elm -const Elm = require("./elm.js"); +import Elm from "./elm"; const elmContainer = document.querySelector("#elm-container"); const platformer = document.querySelector("#platformer"); From ac467e726e599cb47c5e70343de9017fecd53faa Mon Sep 17 00:00:00 2001 From: Bijan Date: Tue, 2 Jan 2018 22:13:53 -0500 Subject: [PATCH 02/23] Add images for character direction changes --- assets/static/images/character-left.gif | Bin 0 -> 1658 bytes assets/static/images/character-right.gif | Bin 0 -> 4098 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/static/images/character-left.gif create mode 100644 assets/static/images/character-right.gif diff --git a/assets/static/images/character-left.gif b/assets/static/images/character-left.gif new file mode 100644 index 0000000000000000000000000000000000000000..ce49afe9415b9363bdba585de16132274d05c1be GIT binary patch literal 1658 zcmV-=28H=YNk%v~VT}O10HOc@0002P2?-Toz5mRa|Ns900000000000A^8Le2ml2D zEC2ui0F3~=000C3Si0Q)Fv>}*y*TU5yZ>M)j$~<`XsWJk>%MR-&vb3yc&_h!@BhG{ za7Zi~kI1BQ$!t2G&?I0=ty-_ss13{Qdb3*8cTBd1Mdh>_aPF+%T8te}im+5QKk*4~U714UBq@4v>+O3Y3+Wn0b~3n}~d$lxL!wQKh0zsHIG+s7kJ{ zNU^I&w6aCFxI?In;M~){ zUE>4a*D+NIr#kmw&OR@9}a=@5RxO95ZXU@0nedJr!OL? zhY|}`naI)qB7lw>Gu==~(F#bASWYgqHWFgUDJnUpROzl|#)dFs=G#bfkPebFb-w)A zljRndIEe~P8nfonE(D@VB@lC})u~H|?)380%hj(Fzlycm^vc#Q_n?lI8g}j2vL-$5 z>B_}z)V6l7!sR)1F5N46@#e)^ly6eMf2#yWH&}0BDrfERsB2enW2%pH_Px@B^3@{12FS3w`PvY2$4(}1n2F0IxrY1(Kz$Np?K^6s^_OPbcrT6S-_#ckhyO(bSWUmahp&iCJ^goi#} ze|h)+aKK02bB573Aa?-1A)tQ*Lgw3bZ|$YVf=UGk5?Iu&@xyEwj&m7d-3`afd;@d{ z&WCJe$Q3mea${mQDBh)FZX@#NqGKM$7@T)C7FZ&VC+aw(WIZ|kBrPPry$FSr?fFD3ed885p2(z|Yw$hMnr!F2MAFs}a^d6K|=x(o542bW2) z!T__Irp6GT>oKt$7`x}T`^Jbdt@R4E>LD?&ys67pIa~9}7N}fk%Q#yL^TJD_Oo7kC zHfwOu4WGKRc|AWJ?Z#ABDfPE~Ts^UzRadR`)}9LTb!1(8J+?`*>vki=5q` z(v<_tP^p|Do%FAr>s*nn@_;@@>86upVd}oF&ic@<@A!G_U(8NBQmUWcyBE4U?K|$Z z4@r9P84v&Z?aSwmeDcK?ebeu3Kksn#S;5{s@Yav4yzSo`FJ<>%G7moXOA$`K)!|FO zO}6T<4=eA))4zT7@?UHJ`THZz!13~1mFU){ne5H$664FC0KcX^o;@Og0X(257-&BK zEdYS)TcF|~_dfSc;tB!?p$JDv!V;SBgeVN52190^0cz`mR!E@?XGlW5yxbVkrHH zM@y#Cj;?&ACs&}!PU3BXu~Z`~Pf$x)-m;gzG}@g;sY_TAvzW6~0Wig=N=NQ817uib z9AM}LXO=Mn(X?hVud>eVFDyGEAZqLKH<2dos#2 zixQMU9M$L;LK-xW_H&{nwaZ3RD$;Rs6QwL2i$wof&za&gqBixXLj#ISo~Epz1xNq@ EJBIu_p#T5? literal 0 HcmV?d00001 diff --git a/assets/static/images/character-right.gif b/assets/static/images/character-right.gif new file mode 100644 index 0000000000000000000000000000000000000000..cb8697350c6775f558300d8bea8d589dd9d83bc7 GIT binary patch literal 4098 zcmeIz_ct2~8wc7KXWmZesOWp z#mWkL+V*R|%%#BphXCY)U0>4#rJ-i1DJLTfWcb+{a>~TY@^iv|3H&+}01F+!z#^bs zmDdr>!XstVUxn)m`~9|VmUcD1CyMXB>ui5@eqWrhMx=mFO#vzC4vJ_)swo^ylXV!# z(y1*P&V;|-nkCg1kLIcdvkB_f5ytYhlcY5H@=7L(4f7RjDMrMpQj22aa%_F6Il=mN zpx|Q#Rf{mk32&7svvTVa47fd;{UKGY*(aAGk8P}+X=6HHyCPsAi|z#eHlfL(x^Led zdkYw=oF?tqVRBbVpO{`VHj)wXfeQQU!A4$@!PyfPnuObwvR&7-q@VW)rNCjOefIT3 zkGb}TK+cyRAA0um#(@_~1P}*n*xYrHY8#EbHQi=k9B8UIzsp!b_W;KULiGj~NzUIg z+FOPOcW2_re=MjxIXSBBCFg#k1{|K-*55J%B9G9|7egiNf{gDG%%22`_2-3LuQ*N) z))$1^hR$mCKtg|GX5kG92p|ZXh>P>tzghtrI6i;GFikd9=REthmLRr6vV<^2y{Z{+bz67J4mG+l#s8x$qWT9Z8?XeNt-Ou zL9ixA-)&e!|GckgOR&|M%*TD#7OKVSt8{4V7-Ne^#V*p)C31Ye0n-tpP7w_z?emxB za}LfFhI2JqO7<{&^hxw~xwMM*f&BbED{ox&l_de;vvU3O5z6;^WHffh%?-Ho4n~wX zXP~xH#8B=L{eLlaqos_t4%uQ{a|L6{x7}f}I+b3D6F}9u+Av{$-oLCg{8DPCnY7Pq zt?bf3-sTQ&?%q?gGUv{wuJyYL`Lf367`o%lHofe*=UT%@9qP!!gM4vuOu|AcuONIf znnz}8NpL-TM>85o_n8Ui$;n@05=Nh_M!nI;t;J~t?tR6X(-E6C=`xV5pi;EtHr`ss zW78*>da`DHOnpnyH$vA?Y4*vd-##blSuQqxYWLgeyh-ujN>KaeORhrI{g@yt<*Lk# zy?)eoTw{HUJ{RlquJw@Qkkj6?So!Yo#47DBZh3Ou60*DGCv@7-_$Y`}>3cYsaZV+- zGqjKT@B4-C#)IE)S7UT{4i@%K-lUzDUz6f(72XnOQS+1x2@&JKv207gndV2{v#LE~ z$i;VpoZ^vP)~41rC+hq%dtvnYgT+L6KNxb2D@65)2vB|lA;SCT-yFvH>t4*7SW-_o z>TKY8;YMnAN4%uPY$k6bwp%8a>aY0~GFzqID~I2W=3>g{uiZ#dU>u9SZbAmF-|1DP zcn0}|?J|Wjg-uaYGl~Zk*cOiW}gc>5~=Vz;@W! zOF6t9K%>Rdmk-1eY!Jhy>ZtTCj3^B1GVI3eku;RNcCTeqZzN(OYWRm3qD^PmGiM@_ zk>rRNZc@7Ok|#Epp6rYOIgff%%4FlRBp$^`Ho|7)KG2>^aGa%&+?c*EbtRner-bfU zq}3swFJ?paRJ+v?e}FS25wx{*#~Crn5!4|=SKWc})SAf$Q6^t_AY0=Zlw`bw*xUcIx()uP#OroRab17LNxPiHp!9eF0t?$ zFzb^tBKm5I8bjPHU5I89HKOIN@{-o1t?87{_2sVx-E2)?nK!R_mHO%F*d5W#%bC61 zEsshK_9-ge3St#tLD;kO>AFt-hSG>^82=*?%g^>^Wu7V+q@thYc!B~EZ<^y+sB79^ z^!a1-u)**u+nvQ`14$P*MRl=dZS@56D+YD&0|(jD|Fy0LAq-rav4YDc5IiQj1Z z=09Aza87LeRByG!wte#{11Hvg!Kh%4682|tP literal 0 HcmV?d00001 From 0640c6bbec52f5c88c4e68aa72fb6d7cf31bdb47 Mon Sep 17 00:00:00 2001 From: Bijan Date: Tue, 2 Jan 2018 22:27:58 -0500 Subject: [PATCH 03/23] Sync Platformer game with book content The demo application and the book had diverged slightly. This commit should contain all the updates to the Platformer.elm file so that the demo follows the book content. And this will allow me to push some additional updates soon. --- assets/elm/Platformer.elm | 134 +++++++++++++++---------------- assets/js/elm.js | 165 +++++++++++++------------------------- 2 files changed, 119 insertions(+), 180 deletions(-) diff --git a/assets/elm/Platformer.elm b/assets/elm/Platformer.elm index 9a15e3e..cf708be 100644 --- a/assets/elm/Platformer.elm +++ b/assets/elm/Platformer.elm @@ -39,6 +39,11 @@ main = -- MODEL +type Direction + = Left + | Right + + type GameState = StartScreen | Playing @@ -46,6 +51,13 @@ type GameState | GameOver +type alias Gameplay = + { gameId : Int + , playerId : Int + , playerScore : Int + } + + type alias Player = { displayName : Maybe String , id : Int @@ -55,36 +67,38 @@ type alias Player = type alias Model = - { errors : String - , gameId : Int - , gameState : GameState + { characterDirection : Direction , characterPositionX : Int , characterPositionY : Int + , errors : String + , gameId : Int + , gameplays : List Gameplay + , gameState : GameState , itemPositionX : Int , itemPositionY : Int , itemsCollected : Int , phxSocket : Phoenix.Socket.Socket Msg , playersList : List Player , playerScore : Int - , playerScores : List Score , timeRemaining : Int } initialModel : Flags -> Model initialModel flags = - { errors = "" - , gameId = 1 - , gameState = StartScreen + { characterDirection = Right , characterPositionX = 50 , characterPositionY = 300 + , errors = "" + , gameId = 1 + , gameplays = [] + , gameState = StartScreen , itemPositionX = 150 , itemPositionY = 300 , itemsCollected = 0 , phxSocket = initialSocketJoin flags , playersList = [] , playerScore = 0 - , playerScores = [] , timeRemaining = 10 } @@ -98,9 +112,9 @@ initialSocket flags = prodSocketServer = "wss://elixir-elm-tutorial.herokuapp.com/socket/websocket?token=" ++ flags.token in - Phoenix.Socket.init prodSocketServer + Phoenix.Socket.init devSocketServer |> Phoenix.Socket.withDebug - |> Phoenix.Socket.on "shout" "score:platformer" SendScore + |> Phoenix.Socket.on "save_score" "score:platformer" SaveScore |> Phoenix.Socket.on "save_score" "score:platformer" ReceiveScoreChanges |> Phoenix.Socket.join initialChannel @@ -182,9 +196,6 @@ type Msg | SaveScore Encode.Value | SaveScoreError Encode.Value | SaveScoreRequest - | SendScore Encode.Value - | SendScoreError Encode.Value - | SendScoreRequest | SetNewItemPositionX Int | TimeUpdate Time @@ -214,10 +225,11 @@ update msg model = 32 -> if model.gameState /= Playing then ( { model - | gameState = Playing + | characterDirection = Right , characterPositionX = 50 - , playerScore = 0 , itemsCollected = 0 + , gameState = Playing + , playerScore = 0 , timeRemaining = 10 } , Cmd.none @@ -227,13 +239,23 @@ update msg model = 37 -> if model.gameState == Playing then - ( { model | characterPositionX = model.characterPositionX - 15 }, Cmd.none ) + ( { model + | characterDirection = Left + , characterPositionX = model.characterPositionX - 15 + } + , Cmd.none + ) else ( model, Cmd.none ) 39 -> if model.gameState == Playing then - ( { model | characterPositionX = model.characterPositionX + 15 }, Cmd.none ) + ( { model + | characterDirection = Right + , characterPositionX = model.characterPositionX + 15 + } + , Cmd.none + ) else ( model, Cmd.none ) @@ -252,7 +274,7 @@ update msg model = ReceiveScoreChanges raw -> case Decode.decodeValue scoreDecoder raw of Ok scoreChange -> - ( { model | playerScores = scoreChange :: model.playerScores }, Cmd.none ) + ( { model | gameplays = scoreChange :: model.gameplays }, Cmd.none ) Err message -> ( { model | errors = message }, Cmd.none ) @@ -261,7 +283,7 @@ update msg model = ( model, Cmd.none ) SaveScoreError message -> - Debug.log "Error saveing score over socket." + Debug.log "Error saving score over socket." ( model, Cmd.none ) SaveScoreRequest -> @@ -282,31 +304,6 @@ update msg model = , Cmd.map PhoenixMsg phxCmd ) - SendScore value -> - ( model, Cmd.none ) - - SendScoreError message -> - Debug.log "Error sending score over socket." - ( model, Cmd.none ) - - SendScoreRequest -> - let - payload = - Encode.object [ ( "player_score", Encode.int model.playerScore ) ] - - phxPush = - Phoenix.Push.init "shout" "score:platformer" - |> Phoenix.Push.withPayload payload - |> Phoenix.Push.onOk SendScore - |> Phoenix.Push.onError SendScoreError - - ( phxSocket, phxCmd ) = - Phoenix.Socket.push phxPush model.phxSocket - in - ( { model | phxSocket = phxSocket } - , Cmd.map PhoenixMsg phxCmd - ) - SetNewItemPositionX newPositionX -> ( { model | itemPositionX = newPositionX }, Cmd.none ) @@ -363,20 +360,8 @@ view : Model -> Html Msg view model = div [] [ viewGame model - , viewSendScoreButton , viewSaveScoreButton - , viewPlayerScoresIndex model - ] - - -viewSendScoreButton : Html Msg -viewSendScoreButton = - div [] - [ button - [ onClick SendScoreRequest - , Html.Attributes.class "btn btn-primary" - ] - [ text "Send Score" ] + , viewGameplaysIndex model ] @@ -391,21 +376,21 @@ viewSaveScoreButton = ] -viewPlayerScoresIndex : Model -> Html Msg -viewPlayerScoresIndex model = - if List.isEmpty model.playerScores then +viewGameplaysIndex : Model -> Html Msg +viewGameplaysIndex model = + if List.isEmpty model.gameplays then div [] [] else div [ Html.Attributes.class "players-index" ] [ h1 [ Html.Attributes.class "players-section" ] [ text "Player Scores" ] - , viewPlayerScoresList model.playerScores + , viewPlayerScoresList model.gameplays ] viewPlayerScoresList : List Score -> Html Msg viewPlayerScoresList scores = div [ Html.Attributes.class "players-list panel panel-info" ] - [ div [ Html.Attributes.class "panel-heading" ] [ text "Leaderboard" ] + [ div [ Html.Attributes.class "panel-heading" ] [ text "Scores" ] , ul [ Html.Attributes.class "list-group" ] (List.map viewPlayerScoreItem scores) ] @@ -527,14 +512,23 @@ viewGameGround = viewCharacter : Model -> Svg Msg viewCharacter model = - image - [ xlinkHref "/images/character.gif" - , x (toString model.characterPositionX) - , y (toString model.characterPositionY) - , width "50" - , height "50" - ] - [] + let + characterImage = + case model.characterDirection of + Left -> + "/images/character-left.gif" + + Right -> + "/images/character-right.gif" + in + image + [ xlinkHref characterImage + , x (toString model.characterPositionX) + , y (toString model.characterPositionY) + , width "50" + , height "50" + ] + [] viewItem : Model -> Svg Msg diff --git a/assets/js/elm.js b/assets/js/elm.js index a865de9..0b5babb 100644 --- a/assets/js/elm.js +++ b/assets/js/elm.js @@ -16139,11 +16139,19 @@ var _user$project$Platformer$viewItem = function (model) { {ctor: '[]'}); }; var _user$project$Platformer$viewCharacter = function (model) { + var characterImage = function () { + var _p0 = model.characterDirection; + if (_p0.ctor === 'Left') { + return '/images/character-left.gif'; + } else { + return '/images/character-right.gif'; + } + }(); return A2( _elm_lang$svg$Svg$image, { ctor: '::', - _0: _elm_lang$svg$Svg_Attributes$xlinkHref('/images/character.gif'), + _0: _elm_lang$svg$Svg_Attributes$xlinkHref(characterImage), _1: { ctor: '::', _0: _elm_lang$svg$Svg_Attributes$x( @@ -16271,8 +16279,8 @@ var _user$project$Platformer$viewStartScreenText = A2( } }); var _user$project$Platformer$viewGameState = function (model) { - var _p0 = model.gameState; - switch (_p0.ctor) { + var _p1 = model.gameState; + switch (_p1.ctor) { case 'StartScreen': return { ctor: '::', @@ -16462,7 +16470,7 @@ var _user$project$Platformer$viewPlayerScoresList = function (scores) { }, { ctor: '::', - _0: _elm_lang$svg$Svg$text('Leaderboard'), + _0: _elm_lang$svg$Svg$text('Scores'), _1: {ctor: '[]'} }), _1: { @@ -16479,8 +16487,8 @@ var _user$project$Platformer$viewPlayerScoresList = function (scores) { } }); }; -var _user$project$Platformer$viewPlayerScoresIndex = function (model) { - return _elm_lang$core$List$isEmpty(model.playerScores) ? A2( +var _user$project$Platformer$viewGameplaysIndex = function (model) { + return _elm_lang$core$List$isEmpty(model.gameplays) ? A2( _elm_lang$html$Html$div, {ctor: '[]'}, {ctor: '[]'}) : A2( @@ -16506,7 +16514,7 @@ var _user$project$Platformer$viewPlayerScoresIndex = function (model) { }), _1: { ctor: '::', - _0: _user$project$Platformer$viewPlayerScoresList(model.playerScores), + _0: _user$project$Platformer$viewPlayerScoresList(model.gameplays), _1: {ctor: '[]'} } }); @@ -16521,6 +16529,10 @@ var _user$project$Platformer$initialChannel = _fbonetti$elm_phoenix_socket$Phoen var _user$project$Platformer$Flags = function (a) { return {token: a}; }; +var _user$project$Platformer$Gameplay = F3( + function (a, b, c) { + return {gameId: a, playerId: b, playerScore: c}; + }); var _user$project$Platformer$Player = F4( function (a, b, c, d) { return {displayName: a, id: b, score: c, username: d}; @@ -16554,7 +16566,9 @@ var _user$project$Platformer$Model = function (a) { return function (k) { return function (l) { return function (m) { - return {errors: a, gameId: b, gameState: c, characterPositionX: d, characterPositionY: e, itemPositionX: f, itemPositionY: g, itemsCollected: h, phxSocket: i, playersList: j, playerScore: k, playerScores: l, timeRemaining: m}; + return function (n) { + return {characterDirection: a, characterPositionX: b, characterPositionY: c, errors: d, gameId: e, gameplays: f, gameState: g, itemPositionX: h, itemPositionY: i, itemsCollected: j, phxSocket: k, playersList: l, playerScore: m, timeRemaining: n}; + }; }; }; }; @@ -16578,6 +16592,8 @@ var _user$project$Platformer$scoreDecoder = A4( A2(_elm_lang$core$Json_Decode$field, 'game_id', _elm_lang$core$Json_Decode$int), A2(_elm_lang$core$Json_Decode$field, 'player_id', _elm_lang$core$Json_Decode$int), A2(_elm_lang$core$Json_Decode$field, 'player_score', _elm_lang$core$Json_Decode$int)); +var _user$project$Platformer$Right = {ctor: 'Right'}; +var _user$project$Platformer$Left = {ctor: 'Left'}; var _user$project$Platformer$GameOver = {ctor: 'GameOver'}; var _user$project$Platformer$Success = {ctor: 'Success'}; var _user$project$Platformer$Playing = {ctor: 'Playing'}; @@ -16588,36 +16604,6 @@ var _user$project$Platformer$TimeUpdate = function (a) { var _user$project$Platformer$SetNewItemPositionX = function (a) { return {ctor: 'SetNewItemPositionX', _0: a}; }; -var _user$project$Platformer$SendScoreRequest = {ctor: 'SendScoreRequest'}; -var _user$project$Platformer$viewSendScoreButton = A2( - _elm_lang$html$Html$div, - {ctor: '[]'}, - { - ctor: '::', - _0: A2( - _elm_lang$html$Html$button, - { - ctor: '::', - _0: _elm_lang$html$Html_Events$onClick(_user$project$Platformer$SendScoreRequest), - _1: { - ctor: '::', - _0: _elm_lang$html$Html_Attributes$class('btn btn-primary'), - _1: {ctor: '[]'} - } - }, - { - ctor: '::', - _0: _elm_lang$svg$Svg$text('Send Score'), - _1: {ctor: '[]'} - }), - _1: {ctor: '[]'} - }); -var _user$project$Platformer$SendScoreError = function (a) { - return {ctor: 'SendScoreError', _0: a}; -}; -var _user$project$Platformer$SendScore = function (a) { - return {ctor: 'SendScore', _0: a}; -}; var _user$project$Platformer$SaveScoreRequest = {ctor: 'SaveScoreRequest'}; var _user$project$Platformer$viewSaveScoreButton = A2( _elm_lang$html$Html$div, @@ -16651,15 +16637,11 @@ var _user$project$Platformer$view = function (model) { _0: _user$project$Platformer$viewGame(model), _1: { ctor: '::', - _0: _user$project$Platformer$viewSendScoreButton, + _0: _user$project$Platformer$viewSaveScoreButton, _1: { ctor: '::', - _0: _user$project$Platformer$viewSaveScoreButton, - _1: { - ctor: '::', - _0: _user$project$Platformer$viewPlayerScoresIndex(model), - _1: {ctor: '[]'} - } + _0: _user$project$Platformer$viewGameplaysIndex(model), + _1: {ctor: '[]'} } } }); @@ -16686,11 +16668,11 @@ var _user$project$Platformer$initialSocket = function (flags) { _user$project$Platformer$ReceiveScoreChanges, A4( _fbonetti$elm_phoenix_socket$Phoenix_Socket$on, - 'shout', + 'save_score', 'score:platformer', - _user$project$Platformer$SendScore, + _user$project$Platformer$SaveScore, _fbonetti$elm_phoenix_socket$Phoenix_Socket$withDebug( - _fbonetti$elm_phoenix_socket$Phoenix_Socket$init(prodSocketServer))))); + _fbonetti$elm_phoenix_socket$Phoenix_Socket$init(devSocketServer))))); }; var _user$project$Platformer$initialSocketJoin = function (flags) { return _elm_lang$core$Tuple$first( @@ -16698,18 +16680,19 @@ var _user$project$Platformer$initialSocketJoin = function (flags) { }; var _user$project$Platformer$initialModel = function (flags) { return { + characterDirection: _user$project$Platformer$Right, + characterPositionX: 50, + characterPositionY: 300, errors: '', gameId: 1, + gameplays: {ctor: '[]'}, gameState: _user$project$Platformer$StartScreen, - characterPositionX: 50, - characterPositionY: 300, itemPositionX: 150, itemPositionY: 300, itemsCollected: 0, phxSocket: _user$project$Platformer$initialSocketJoin(flags), playersList: {ctor: '[]'}, playerScore: 0, - playerScores: {ctor: '[]'}, timeRemaining: 10 }; }; @@ -16732,8 +16715,8 @@ var _user$project$Platformer$init = function (flags) { }; var _user$project$Platformer$update = F2( function (msg, model) { - var _p1 = msg; - switch (_p1.ctor) { + var _p2 = msg; + switch (_p2.ctor) { case 'NoOp': return {ctor: '_Tuple2', _0: model, _1: _elm_lang$core$Platform_Cmd$none}; case 'CountdownTimer': @@ -16745,13 +16728,13 @@ var _user$project$Platformer$update = F2( _1: _elm_lang$core$Platform_Cmd$none } : {ctor: '_Tuple2', _0: model, _1: _elm_lang$core$Platform_Cmd$none}; case 'FetchPlayersList': - var _p2 = _p1._0; - if (_p2.ctor === 'Ok') { + var _p3 = _p2._0; + if (_p3.ctor === 'Ok') { return { ctor: '_Tuple2', _0: _elm_lang$core$Native_Utils.update( model, - {playersList: _p2._0}), + {playersList: _p3._0}), _1: _elm_lang$core$Platform_Cmd$none }; } else { @@ -16760,20 +16743,20 @@ var _user$project$Platformer$update = F2( _0: _elm_lang$core$Native_Utils.update( model, { - errors: _elm_lang$core$Basics$toString(_p2._0) + errors: _elm_lang$core$Basics$toString(_p3._0) }), _1: _elm_lang$core$Platform_Cmd$none }; } case 'KeyDown': - var _p3 = _p1._0; - switch (_p3) { + var _p4 = _p2._0; + switch (_p4) { case 32: return (!_elm_lang$core$Native_Utils.eq(model.gameState, _user$project$Platformer$Playing)) ? { ctor: '_Tuple2', _0: _elm_lang$core$Native_Utils.update( model, - {gameState: _user$project$Platformer$Playing, characterPositionX: 50, playerScore: 0, itemsCollected: 0, timeRemaining: 10}), + {characterDirection: _user$project$Platformer$Right, characterPositionX: 50, itemsCollected: 0, gameState: _user$project$Platformer$Playing, playerScore: 0, timeRemaining: 10}), _1: _elm_lang$core$Platform_Cmd$none } : {ctor: '_Tuple2', _0: model, _1: _elm_lang$core$Platform_Cmd$none}; case 37: @@ -16781,7 +16764,7 @@ var _user$project$Platformer$update = F2( ctor: '_Tuple2', _0: _elm_lang$core$Native_Utils.update( model, - {characterPositionX: model.characterPositionX - 15}), + {characterDirection: _user$project$Platformer$Left, characterPositionX: model.characterPositionX - 15}), _1: _elm_lang$core$Platform_Cmd$none } : {ctor: '_Tuple2', _0: model, _1: _elm_lang$core$Platform_Cmd$none}; case 39: @@ -16789,16 +16772,16 @@ var _user$project$Platformer$update = F2( ctor: '_Tuple2', _0: _elm_lang$core$Native_Utils.update( model, - {characterPositionX: model.characterPositionX + 15}), + {characterDirection: _user$project$Platformer$Right, characterPositionX: model.characterPositionX + 15}), _1: _elm_lang$core$Platform_Cmd$none } : {ctor: '_Tuple2', _0: model, _1: _elm_lang$core$Platform_Cmd$none}; default: return {ctor: '_Tuple2', _0: model, _1: _elm_lang$core$Platform_Cmd$none}; } case 'PhoenixMsg': - var _p4 = A2(_fbonetti$elm_phoenix_socket$Phoenix_Socket$update, _p1._0, model.phxSocket); - var phxSocket = _p4._0; - var phxCmd = _p4._1; + var _p5 = A2(_fbonetti$elm_phoenix_socket$Phoenix_Socket$update, _p2._0, model.phxSocket); + var phxSocket = _p5._0; + var phxCmd = _p5._1; return { ctor: '_Tuple2', _0: _elm_lang$core$Native_Utils.update( @@ -16807,14 +16790,14 @@ var _user$project$Platformer$update = F2( _1: A2(_elm_lang$core$Platform_Cmd$map, _user$project$Platformer$PhoenixMsg, phxCmd) }; case 'ReceiveScoreChanges': - var _p5 = A2(_elm_lang$core$Json_Decode$decodeValue, _user$project$Platformer$scoreDecoder, _p1._0); - if (_p5.ctor === 'Ok') { + var _p6 = A2(_elm_lang$core$Json_Decode$decodeValue, _user$project$Platformer$scoreDecoder, _p2._0); + if (_p6.ctor === 'Ok') { return { ctor: '_Tuple2', _0: _elm_lang$core$Native_Utils.update( model, { - playerScores: {ctor: '::', _0: _p5._0, _1: model.playerScores} + gameplays: {ctor: '::', _0: _p6._0, _1: model.gameplays} }), _1: _elm_lang$core$Platform_Cmd$none }; @@ -16823,7 +16806,7 @@ var _user$project$Platformer$update = F2( ctor: '_Tuple2', _0: _elm_lang$core$Native_Utils.update( model, - {errors: _p5._0}), + {errors: _p6._0}), _1: _elm_lang$core$Platform_Cmd$none }; } @@ -16832,7 +16815,7 @@ var _user$project$Platformer$update = F2( case 'SaveScoreError': return A2( _elm_lang$core$Debug$log, - 'Error saveing score over socket.', + 'Error saving score over socket.', {ctor: '_Tuple2', _0: model, _1: _elm_lang$core$Platform_Cmd$none}); case 'SaveScoreRequest': var payload = _elm_lang$core$Json_Encode$object( @@ -16855,44 +16838,6 @@ var _user$project$Platformer$update = F2( _fbonetti$elm_phoenix_socket$Phoenix_Push$withPayload, payload, A2(_fbonetti$elm_phoenix_socket$Phoenix_Push$init, 'save_score', 'score:platformer')))); - var _p6 = A2(_fbonetti$elm_phoenix_socket$Phoenix_Socket$push, phxPush, model.phxSocket); - var phxSocket = _p6._0; - var phxCmd = _p6._1; - return { - ctor: '_Tuple2', - _0: _elm_lang$core$Native_Utils.update( - model, - {phxSocket: phxSocket}), - _1: A2(_elm_lang$core$Platform_Cmd$map, _user$project$Platformer$PhoenixMsg, phxCmd) - }; - case 'SendScore': - return {ctor: '_Tuple2', _0: model, _1: _elm_lang$core$Platform_Cmd$none}; - case 'SendScoreError': - return A2( - _elm_lang$core$Debug$log, - 'Error sending score over socket.', - {ctor: '_Tuple2', _0: model, _1: _elm_lang$core$Platform_Cmd$none}); - case 'SendScoreRequest': - var payload = _elm_lang$core$Json_Encode$object( - { - ctor: '::', - _0: { - ctor: '_Tuple2', - _0: 'player_score', - _1: _elm_lang$core$Json_Encode$int(model.playerScore) - }, - _1: {ctor: '[]'} - }); - var phxPush = A2( - _fbonetti$elm_phoenix_socket$Phoenix_Push$onError, - _user$project$Platformer$SendScoreError, - A2( - _fbonetti$elm_phoenix_socket$Phoenix_Push$onOk, - _user$project$Platformer$SendScore, - A2( - _fbonetti$elm_phoenix_socket$Phoenix_Push$withPayload, - payload, - A2(_fbonetti$elm_phoenix_socket$Phoenix_Push$init, 'shout', 'score:platformer')))); var _p7 = A2(_fbonetti$elm_phoenix_socket$Phoenix_Socket$push, phxPush, model.phxSocket); var phxSocket = _p7._0; var phxCmd = _p7._1; @@ -16908,7 +16853,7 @@ var _user$project$Platformer$update = F2( ctor: '_Tuple2', _0: _elm_lang$core$Native_Utils.update( model, - {itemPositionX: _p1._0}), + {itemPositionX: _p2._0}), _1: _elm_lang$core$Platform_Cmd$none }; default: @@ -16987,7 +16932,7 @@ if (typeof _user$project$Main$main !== 'undefined') { } Elm['Platformer'] = Elm['Platformer'] || {}; if (typeof _user$project$Platformer$main !== 'undefined') { - _user$project$Platformer$main(Elm['Platformer'], 'Platformer', {"types":{"unions":{"Dict.LeafColor":{"args":[],"tags":{"LBBlack":[],"LBlack":[]}},"Platformer.Msg":{"args":[],"tags":{"SetNewItemPositionX":["Int"],"SendScoreRequest":[],"SaveScoreRequest":[],"CountdownTimer":["Time.Time"],"FetchPlayersList":["Result.Result Http.Error (List Platformer.Player)"],"SaveScore":["Json.Encode.Value"],"SendScore":["Json.Encode.Value"],"PhoenixMsg":["Phoenix.Socket.Msg Platformer.Msg"],"TimeUpdate":["Time.Time"],"SendScoreError":["Json.Encode.Value"],"KeyDown":["Keyboard.KeyCode"],"ReceiveScoreChanges":["Json.Encode.Value"],"NoOp":[],"SaveScoreError":["Json.Encode.Value"]}},"Json.Encode.Value":{"args":[],"tags":{"Value":[]}},"Dict.Dict":{"args":["k","v"],"tags":{"RBNode_elm_builtin":["Dict.NColor","k","v","Dict.Dict k v","Dict.Dict k v"],"RBEmpty_elm_builtin":["Dict.LeafColor"]}},"Maybe.Maybe":{"args":["a"],"tags":{"Just":["a"],"Nothing":[]}},"Dict.NColor":{"args":[],"tags":{"BBlack":[],"Red":[],"NBlack":[],"Black":[]}},"Http.Error":{"args":[],"tags":{"BadUrl":["String"],"NetworkError":[],"Timeout":[],"BadStatus":["Http.Response String"],"BadPayload":["String","Http.Response String"]}},"Result.Result":{"args":["error","value"],"tags":{"Ok":["value"],"Err":["error"]}},"Phoenix.Socket.Msg":{"args":["msg"],"tags":{"ChannelErrored":["String"],"ChannelClosed":["String"],"ExternalMsg":["msg"],"ChannelJoined":["String"],"Heartbeat":["Time.Time"],"NoOp":[],"ReceiveReply":["String","Int"]}}},"aliases":{"Http.Response":{"args":["body"],"type":"{ url : String , status : { code : Int, message : String } , headers : Dict.Dict String String , body : body }"},"Keyboard.KeyCode":{"args":[],"type":"Int"},"Platformer.Player":{"args":[],"type":"{ displayName : Maybe.Maybe String , id : Int , score : Int , username : String }"},"Time.Time":{"args":[],"type":"Float"}},"message":"Platformer.Msg"},"versions":{"elm":"0.18.0"}}); + _user$project$Platformer$main(Elm['Platformer'], 'Platformer', {"types":{"unions":{"Dict.LeafColor":{"args":[],"tags":{"LBBlack":[],"LBlack":[]}},"Platformer.Msg":{"args":[],"tags":{"SetNewItemPositionX":["Int"],"SaveScoreRequest":[],"CountdownTimer":["Time.Time"],"FetchPlayersList":["Result.Result Http.Error (List Platformer.Player)"],"SaveScore":["Json.Encode.Value"],"PhoenixMsg":["Phoenix.Socket.Msg Platformer.Msg"],"TimeUpdate":["Time.Time"],"KeyDown":["Keyboard.KeyCode"],"ReceiveScoreChanges":["Json.Encode.Value"],"NoOp":[],"SaveScoreError":["Json.Encode.Value"]}},"Json.Encode.Value":{"args":[],"tags":{"Value":[]}},"Dict.Dict":{"args":["k","v"],"tags":{"RBNode_elm_builtin":["Dict.NColor","k","v","Dict.Dict k v","Dict.Dict k v"],"RBEmpty_elm_builtin":["Dict.LeafColor"]}},"Maybe.Maybe":{"args":["a"],"tags":{"Just":["a"],"Nothing":[]}},"Dict.NColor":{"args":[],"tags":{"BBlack":[],"Red":[],"NBlack":[],"Black":[]}},"Http.Error":{"args":[],"tags":{"BadUrl":["String"],"NetworkError":[],"Timeout":[],"BadStatus":["Http.Response String"],"BadPayload":["String","Http.Response String"]}},"Result.Result":{"args":["error","value"],"tags":{"Ok":["value"],"Err":["error"]}},"Phoenix.Socket.Msg":{"args":["msg"],"tags":{"ChannelErrored":["String"],"ChannelClosed":["String"],"ExternalMsg":["msg"],"ChannelJoined":["String"],"Heartbeat":["Time.Time"],"NoOp":[],"ReceiveReply":["String","Int"]}}},"aliases":{"Http.Response":{"args":["body"],"type":"{ url : String , status : { code : Int, message : String } , headers : Dict.Dict String String , body : body }"},"Keyboard.KeyCode":{"args":[],"type":"Int"},"Platformer.Player":{"args":[],"type":"{ displayName : Maybe.Maybe String , id : Int , score : Int , username : String }"},"Time.Time":{"args":[],"type":"Float"}},"message":"Platformer.Msg"},"versions":{"elm":"0.18.0"}}); } if (typeof define === "function" && define['amd']) From 8753da4c8ce8b9fb86c0de2a4f455cdf14958cc5 Mon Sep 17 00:00:00 2001 From: Bijan Date: Tue, 2 Jan 2018 22:52:44 -0500 Subject: [PATCH 04/23] Generate gameplay resources --- lib/platform/products/gameplay.ex | 2 + lib/platform/products/products.ex | 105 ++++++++++++++++-- .../controllers/gameplay_controller.ex | 42 +++++++ lib/platform_web/router.ex | 3 +- lib/platform_web/views/gameplay_view.ex | 17 +++ .../20170826154100_create_games.exs | 8 -- .../20180103033750_create_gameplays.exs | 16 +++ test/platform/products/products_test.exs | 60 ++++++++++ .../controllers/gameplay_controller_test.exs | 79 +++++++++++++ 9 files changed, 314 insertions(+), 18 deletions(-) create mode 100644 lib/platform_web/controllers/gameplay_controller.ex create mode 100644 lib/platform_web/views/gameplay_view.ex create mode 100644 priv/repo/migrations/20180103033750_create_gameplays.exs create mode 100644 test/platform_web/controllers/gameplay_controller_test.exs diff --git a/lib/platform/products/gameplay.ex b/lib/platform/products/gameplay.ex index d28398f..afce1c0 100644 --- a/lib/platform/products/gameplay.ex +++ b/lib/platform/products/gameplay.ex @@ -19,5 +19,7 @@ defmodule Platform.Products.Gameplay do gameplay |> cast(attrs, [:game_id, :player_id, :player_score]) |> validate_required([:game_id, :player_id, :player_score]) + |> foreign_key_constraint(:game_id) + |> foreign_key_constraint(:player_id) end end diff --git a/lib/platform/products/products.ex b/lib/platform/products/products.ex index 747f443..fc2fd58 100644 --- a/lib/platform/products/products.ex +++ b/lib/platform/products/products.ex @@ -22,10 +22,6 @@ defmodule Platform.Products do Repo.all(Game) end - def list_gameplays do - Repo.all(Gameplay) - end - @doc """ Gets a single game. @@ -66,11 +62,6 @@ defmodule Platform.Products do |> Repo.insert() end - def create_gameplay(attrs \\ %{}) do - %Gameplay{} - |> Gameplay.changeset(attrs) - |> Repo.insert() - end @doc """ Updates a game. @@ -118,4 +109,100 @@ defmodule Platform.Products do def change_game(%Game{} = game) do Game.changeset(game, %{}) end + + alias Platform.Products.Gameplay + + @doc """ + Returns the list of gameplays. + + ## Examples + + iex> list_gameplays() + [%Gameplay{}, ...] + + """ + def list_gameplays do + Repo.all(Gameplay) + end + + @doc """ + Gets a single gameplay. + + Raises `Ecto.NoResultsError` if the Gameplay does not exist. + + ## Examples + + iex> get_gameplay!(123) + %Gameplay{} + + iex> get_gameplay!(456) + ** (Ecto.NoResultsError) + + """ + def get_gameplay!(id), do: Repo.get!(Gameplay, id) + + @doc """ + Creates a gameplay. + + ## Examples + + iex> create_gameplay(%{field: value}) + {:ok, %Gameplay{}} + + iex> create_gameplay(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_gameplay(attrs \\ %{}) do + %Gameplay{} + |> Gameplay.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a gameplay. + + ## Examples + + iex> update_gameplay(gameplay, %{field: new_value}) + {:ok, %Gameplay{}} + + iex> update_gameplay(gameplay, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_gameplay(%Gameplay{} = gameplay, attrs) do + gameplay + |> Gameplay.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a Gameplay. + + ## Examples + + iex> delete_gameplay(gameplay) + {:ok, %Gameplay{}} + + iex> delete_gameplay(gameplay) + {:error, %Ecto.Changeset{}} + + """ + def delete_gameplay(%Gameplay{} = gameplay) do + Repo.delete(gameplay) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking gameplay changes. + + ## Examples + + iex> change_gameplay(gameplay) + %Ecto.Changeset{source: %Gameplay{}} + + """ + def change_gameplay(%Gameplay{} = gameplay) do + Gameplay.changeset(gameplay, %{}) + end end diff --git a/lib/platform_web/controllers/gameplay_controller.ex b/lib/platform_web/controllers/gameplay_controller.ex new file mode 100644 index 0000000..67fcc2d --- /dev/null +++ b/lib/platform_web/controllers/gameplay_controller.ex @@ -0,0 +1,42 @@ +defmodule PlatformWeb.GameplayController do + use PlatformWeb, :controller + + alias Platform.Products + alias Platform.Products.Gameplay + + action_fallback PlatformWeb.FallbackController + + def index(conn, _params) do + gameplays = Products.list_gameplays() + render(conn, "index.json", gameplays: gameplays) + end + + def create(conn, %{"gameplay" => gameplay_params}) do + with {:ok, %Gameplay{} = gameplay} <- Products.create_gameplay(gameplay_params) do + conn + |> put_status(:created) + |> put_resp_header("location", gameplay_path(conn, :show, gameplay)) + |> render("show.json", gameplay: gameplay) + end + end + + def show(conn, %{"id" => id}) do + gameplay = Products.get_gameplay!(id) + render(conn, "show.json", gameplay: gameplay) + end + + def update(conn, %{"id" => id, "gameplay" => gameplay_params}) do + gameplay = Products.get_gameplay!(id) + + with {:ok, %Gameplay{} = gameplay} <- Products.update_gameplay(gameplay, gameplay_params) do + render(conn, "show.json", gameplay: gameplay) + end + end + + def delete(conn, %{"id" => id}) do + gameplay = Products.get_gameplay!(id) + with {:ok, %Gameplay{}} <- Products.delete_gameplay(gameplay) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/lib/platform_web/router.ex b/lib/platform_web/router.ex index de76ace..f9178ae 100644 --- a/lib/platform_web/router.ex +++ b/lib/platform_web/router.ex @@ -27,8 +27,9 @@ defmodule PlatformWeb.Router do scope "/api", PlatformWeb do pipe_through :api - resources "/players", PlayerApiController, except: [:new, :edit] resources "/games", GameController, except: [:new, :edit] + resources "/gameplays", GameplayController, except: [:new, :edit] + resources "/players", PlayerApiController, except: [:new, :edit] end defp put_user_token(conn, _) do diff --git a/lib/platform_web/views/gameplay_view.ex b/lib/platform_web/views/gameplay_view.ex new file mode 100644 index 0000000..9c4cdf7 --- /dev/null +++ b/lib/platform_web/views/gameplay_view.ex @@ -0,0 +1,17 @@ +defmodule PlatformWeb.GameplayView do + use PlatformWeb, :view + alias PlatformWeb.GameplayView + + def render("index.json", %{gameplays: gameplays}) do + %{data: render_many(gameplays, GameplayView, "gameplay.json")} + end + + def render("show.json", %{gameplay: gameplay}) do + %{data: render_one(gameplay, GameplayView, "gameplay.json")} + end + + def render("gameplay.json", %{gameplay: gameplay}) do + %{id: gameplay.id, + player_score: gameplay.player_score} + end +end diff --git a/priv/repo/migrations/20170826154100_create_games.exs b/priv/repo/migrations/20170826154100_create_games.exs index d657eed..2a5821d 100644 --- a/priv/repo/migrations/20170826154100_create_games.exs +++ b/priv/repo/migrations/20170826154100_create_games.exs @@ -10,13 +10,5 @@ defmodule Platform.Repo.Migrations.CreateGames do timestamps() end - - create table(:gameplays) do - add :game_id, references(:games, on_delete: :nothing), null: false - add :player_id, references(:players, on_delete: :nothing), null: false - add :player_score, :integer - - timestamps() - end end end diff --git a/priv/repo/migrations/20180103033750_create_gameplays.exs b/priv/repo/migrations/20180103033750_create_gameplays.exs new file mode 100644 index 0000000..939c73a --- /dev/null +++ b/priv/repo/migrations/20180103033750_create_gameplays.exs @@ -0,0 +1,16 @@ +defmodule Platform.Repo.Migrations.CreateGameplays do + use Ecto.Migration + + def change do + create table(:gameplays) do + add :player_score, :integer + add :game_id, references(:games, on_delete: :nothing) + add :player_id, references(:players, on_delete: :nothing) + + timestamps() + end + + create index(:gameplays, [:game_id]) + create index(:gameplays, [:player_id]) + end +end diff --git a/test/platform/products/products_test.exs b/test/platform/products/products_test.exs index 15e7b00..086088f 100644 --- a/test/platform/products/products_test.exs +++ b/test/platform/products/products_test.exs @@ -68,4 +68,64 @@ defmodule Platform.ProductsTest do assert %Ecto.Changeset{} = Products.change_game(game) end end + + describe "gameplays" do + alias Platform.Products.Gameplay + + @valid_attrs %{game_id: 42, player_id: 42, player_score: 42} + @update_attrs %{game_id: 42, player_id: 42, player_score: 42} + @invalid_attrs %{game_id: nil, player_id: nil, player_score: nil} + + def gameplay_fixture(attrs \\ %{}) do + {:ok, gameplay} = + attrs + |> Enum.into(@valid_attrs) + |> Products.create_gameplay() + + gameplay + end + + test "list_gameplays/0 returns all gameplays" do + gameplay = gameplay_fixture() + assert Products.list_gameplays() == [gameplay] + end + + test "get_gameplay!/1 returns the gameplay with given id" do + gameplay = gameplay_fixture() + assert Products.get_gameplay!(gameplay.id) == gameplay + end + + test "create_gameplay/1 with valid data creates a gameplay" do + assert {:ok, %Gameplay{} = gameplay} = Products.create_gameplay(@valid_attrs) + assert gameplay.player_score == 42 + end + + test "create_gameplay/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Products.create_gameplay(@invalid_attrs) + end + + test "update_gameplay/2 with valid data updates the gameplay" do + gameplay = gameplay_fixture() + assert {:ok, gameplay} = Products.update_gameplay(gameplay, @update_attrs) + assert %Gameplay{} = gameplay + assert gameplay.player_score == 43 + end + + test "update_gameplay/2 with invalid data returns error changeset" do + gameplay = gameplay_fixture() + assert {:error, %Ecto.Changeset{}} = Products.update_gameplay(gameplay, @invalid_attrs) + assert gameplay == Products.get_gameplay!(gameplay.id) + end + + test "delete_gameplay/1 deletes the gameplay" do + gameplay = gameplay_fixture() + assert {:ok, %Gameplay{}} = Products.delete_gameplay(gameplay) + assert_raise Ecto.NoResultsError, fn -> Products.get_gameplay!(gameplay.id) end + end + + test "change_gameplay/1 returns a gameplay changeset" do + gameplay = gameplay_fixture() + assert %Ecto.Changeset{} = Products.change_gameplay(gameplay) + end + end end diff --git a/test/platform_web/controllers/gameplay_controller_test.exs b/test/platform_web/controllers/gameplay_controller_test.exs new file mode 100644 index 0000000..e2419ce --- /dev/null +++ b/test/platform_web/controllers/gameplay_controller_test.exs @@ -0,0 +1,79 @@ +defmodule PlatformWeb.GameplayControllerTest do + use PlatformWeb.ConnCase + + alias Platform.Products + alias Platform.Products.Gameplay + + @create_attrs %{player_score: 42} + @update_attrs %{player_score: 43} + @invalid_attrs %{player_score: nil} + + def fixture(:gameplay) do + {:ok, gameplay} = Products.create_gameplay(@create_attrs) + gameplay + end + + setup %{conn: conn} do + {:ok, conn: put_req_header(conn, "accept", "application/json")} + end + + describe "index" do + test "lists all gameplays", %{conn: conn} do + conn = get conn, gameplay_path(conn, :index) + assert json_response(conn, 200)["data"] == [] + end + end + + describe "create gameplay" do + test "renders gameplay when data is valid", %{conn: conn} do + conn = post conn, gameplay_path(conn, :create), gameplay: @create_attrs + assert %{"id" => id} = json_response(conn, 201)["data"] + + conn = get conn, gameplay_path(conn, :show, id) + assert json_response(conn, 200)["data"] == %{ + "id" => id, + "player_score" => 42} + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post conn, gameplay_path(conn, :create), gameplay: @invalid_attrs + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "update gameplay" do + setup [:create_gameplay] + + test "renders gameplay when data is valid", %{conn: conn, gameplay: %Gameplay{id: id} = gameplay} do + conn = put conn, gameplay_path(conn, :update, gameplay), gameplay: @update_attrs + assert %{"id" => ^id} = json_response(conn, 200)["data"] + + conn = get conn, gameplay_path(conn, :show, id) + assert json_response(conn, 200)["data"] == %{ + "id" => id, + "player_score" => 43} + end + + test "renders errors when data is invalid", %{conn: conn, gameplay: gameplay} do + conn = put conn, gameplay_path(conn, :update, gameplay), gameplay: @invalid_attrs + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "delete gameplay" do + setup [:create_gameplay] + + test "deletes chosen gameplay", %{conn: conn, gameplay: gameplay} do + conn = delete conn, gameplay_path(conn, :delete, gameplay) + assert response(conn, 204) + assert_error_sent 404, fn -> + get conn, gameplay_path(conn, :show, gameplay) + end + end + end + + defp create_gameplay(_) do + gameplay = fixture(:gameplay) + {:ok, gameplay: gameplay} + end +end From e9d562ce0a7f808004179e1c6d994ba6d70c5848 Mon Sep 17 00:00:00 2001 From: Bijan Date: Tue, 2 Jan 2018 22:57:00 -0500 Subject: [PATCH 05/23] Update player's total score on gameplay creation Not sure if it's a good idea to alias the Accounts module here since I think the contexts are separated for a reason. But the idea is to sum the player's total score whenever a new gameplay record is created. --- lib/platform/products/products.ex | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/platform/products/products.ex b/lib/platform/products/products.ex index fc2fd58..f7a365c 100644 --- a/lib/platform/products/products.ex +++ b/lib/platform/products/products.ex @@ -6,6 +6,7 @@ defmodule Platform.Products do import Ecto.Query, warn: false alias Platform.Repo + alias Platform.Accounts alias Platform.Products.Game alias Platform.Products.Gameplay @@ -39,11 +40,6 @@ defmodule Platform.Products do def get_game!(id), do: Repo.get!(Game, id) def get_game_by_slug!(slug), do: Repo.get_by!(Game, slug: slug) - def get_gameplays_by_id!(id) do - query = from gp in "gameplays", where: gp.game_id == ^id, select: [:player_id, :player_score] - Repo.all(query) - end - @doc """ Creates a game. @@ -110,8 +106,6 @@ defmodule Platform.Products do Game.changeset(game, %{}) end - alias Platform.Products.Gameplay - @doc """ Returns the list of gameplays. @@ -140,6 +134,10 @@ defmodule Platform.Products do """ def get_gameplay!(id), do: Repo.get!(Gameplay, id) + def get_gameplays_by_id!(id) do + query = from gp in "gameplays", where: gp.game_id == ^id, select: [:player_id, :player_score] + Repo.all(query) + end @doc """ Creates a gameplay. @@ -154,6 +152,11 @@ defmodule Platform.Products do """ def create_gameplay(attrs \\ %{}) do + # Update player total score. + player = Accounts.get_player!(attrs[:player_id]) + Accounts.update_player(player, %{score: player.score + attrs[:player_score]}) + + # Create gameplay record. %Gameplay{} |> Gameplay.changeset(attrs) |> Repo.insert() From d735feb1684e5f374bab30d68f507f196fa0d7a5 Mon Sep 17 00:00:00 2001 From: Bijan Date: Wed, 3 Jan 2018 00:32:59 -0500 Subject: [PATCH 06/23] Display player name with score --- assets/elm/Platformer.elm | 61 ++++++++++++++++++----- assets/js/elm.js | 101 +++++++++++++++++++++++++++++++------- 2 files changed, 134 insertions(+), 28 deletions(-) diff --git a/assets/elm/Platformer.elm b/assets/elm/Platformer.elm index cf708be..1d88106 100644 --- a/assets/elm/Platformer.elm +++ b/assets/elm/Platformer.elm @@ -138,7 +138,12 @@ initialSocketCommand flags = init : Flags -> ( Model, Cmd Msg ) init flags = - ( initialModel flags, Cmd.map PhoenixMsg (initialSocketCommand flags) ) + ( initialModel flags + , Cmd.batch + [ fetchPlayersList + , Cmd.map PhoenixMsg (initialSocketCommand flags) + ] + ) @@ -383,24 +388,58 @@ viewGameplaysIndex model = else div [ Html.Attributes.class "players-index" ] [ h1 [ Html.Attributes.class "players-section" ] [ text "Player Scores" ] - , viewPlayerScoresList model.gameplays + , viewGameplaysList model ] -viewPlayerScoresList : List Score -> Html Msg -viewPlayerScoresList scores = +viewGameplaysList : Model -> Html Msg +viewGameplaysList model = div [ Html.Attributes.class "players-list panel panel-info" ] [ div [ Html.Attributes.class "panel-heading" ] [ text "Scores" ] - , ul [ Html.Attributes.class "list-group" ] (List.map viewPlayerScoreItem scores) + , ul [ Html.Attributes.class "list-group" ] (List.map (viewPlayerScoreItem model) model.gameplays) ] -viewPlayerScoreItem : Score -> Html Msg -viewPlayerScoreItem score = - li [ Html.Attributes.class "player-item list-group-item" ] - [ strong [] [ text (toString score.playerId) ] - , span [ Html.Attributes.class "badge" ] [ text (toString score.playerScore) ] - ] +anonymousPlayer : Player +anonymousPlayer = + { displayName = Just "Anonymous User" + , id = 0 + , score = 0 + , username = "anonymous" + } + + +viewPlayerScoreItem : Model -> Gameplay -> Html Msg +viewPlayerScoreItem model gameplay = + let + currentPlayer = + model.playersList + |> List.filter (\player -> player.id == gameplay.playerId) + |> List.head + |> Maybe.withDefault anonymousPlayer + + displayName = + Maybe.withDefault currentPlayer.username currentPlayer.displayName + in + li [ Html.Attributes.class "player-item list-group-item" ] + [ strong [] [ text displayName ] + , span [ Html.Attributes.class "badge" ] [ text (toString gameplay.playerScore) ] + ] + + +playersListItem : Player -> Html msg +playersListItem player = + let + displayName = + if player.displayName == Nothing then + player.username + else + Maybe.withDefault "" player.displayName + in + li [ Html.Attributes.class "player-item list-group-item" ] + [ strong [] [ text displayName ] + , span [ Html.Attributes.class "badge" ] [ text (toString player.score) ] + ] viewGame : Model -> Svg Msg diff --git a/assets/js/elm.js b/assets/js/elm.js index 0b5babb..56a0535 100644 --- a/assets/js/elm.js +++ b/assets/js/elm.js @@ -16413,7 +16413,8 @@ var _user$project$Platformer$viewGame = function (model) { }, _user$project$Platformer$viewGameState(model)); }; -var _user$project$Platformer$viewPlayerScoreItem = function (score) { +var _user$project$Platformer$playersListItem = function (player) { + var displayName = _elm_lang$core$Native_Utils.eq(player.displayName, _elm_lang$core$Maybe$Nothing) ? player.username : A2(_elm_lang$core$Maybe$withDefault, '', player.displayName); return A2( _elm_lang$html$Html$li, { @@ -16428,8 +16429,7 @@ var _user$project$Platformer$viewPlayerScoreItem = function (score) { {ctor: '[]'}, { ctor: '::', - _0: _elm_lang$svg$Svg$text( - _elm_lang$core$Basics$toString(score.playerId)), + _0: _elm_lang$svg$Svg$text(displayName), _1: {ctor: '[]'} }), _1: { @@ -16444,14 +16444,69 @@ var _user$project$Platformer$viewPlayerScoreItem = function (score) { { ctor: '::', _0: _elm_lang$svg$Svg$text( - _elm_lang$core$Basics$toString(score.playerScore)), + _elm_lang$core$Basics$toString(player.score)), _1: {ctor: '[]'} }), _1: {ctor: '[]'} } }); }; -var _user$project$Platformer$viewPlayerScoresList = function (scores) { +var _user$project$Platformer$anonymousPlayer = { + displayName: _elm_lang$core$Maybe$Just('Anonymous User'), + id: 0, + score: 0, + username: 'anonymous' +}; +var _user$project$Platformer$viewPlayerScoreItem = F2( + function (model, gameplay) { + var currentPlayer = A2( + _elm_lang$core$Maybe$withDefault, + _user$project$Platformer$anonymousPlayer, + _elm_lang$core$List$head( + A2( + _elm_lang$core$List$filter, + function (player) { + return _elm_lang$core$Native_Utils.eq(player.id, gameplay.playerId); + }, + model.playersList))); + var displayName = A2(_elm_lang$core$Maybe$withDefault, currentPlayer.username, currentPlayer.displayName); + return A2( + _elm_lang$html$Html$li, + { + ctor: '::', + _0: _elm_lang$html$Html_Attributes$class('player-item list-group-item'), + _1: {ctor: '[]'} + }, + { + ctor: '::', + _0: A2( + _elm_lang$html$Html$strong, + {ctor: '[]'}, + { + ctor: '::', + _0: _elm_lang$svg$Svg$text(displayName), + _1: {ctor: '[]'} + }), + _1: { + ctor: '::', + _0: A2( + _elm_lang$html$Html$span, + { + ctor: '::', + _0: _elm_lang$html$Html_Attributes$class('badge'), + _1: {ctor: '[]'} + }, + { + ctor: '::', + _0: _elm_lang$svg$Svg$text( + _elm_lang$core$Basics$toString(gameplay.playerScore)), + _1: {ctor: '[]'} + }), + _1: {ctor: '[]'} + } + }); + }); +var _user$project$Platformer$viewGameplaysList = function (model) { return A2( _elm_lang$html$Html$div, { @@ -16482,7 +16537,10 @@ var _user$project$Platformer$viewPlayerScoresList = function (scores) { _0: _elm_lang$html$Html_Attributes$class('list-group'), _1: {ctor: '[]'} }, - A2(_elm_lang$core$List$map, _user$project$Platformer$viewPlayerScoreItem, scores)), + A2( + _elm_lang$core$List$map, + _user$project$Platformer$viewPlayerScoreItem(model), + model.gameplays)), _1: {ctor: '[]'} } }); @@ -16514,7 +16572,7 @@ var _user$project$Platformer$viewGameplaysIndex = function (model) { }), _1: { ctor: '::', - _0: _user$project$Platformer$viewPlayerScoresList(model.gameplays), + _0: _user$project$Platformer$viewGameplaysList(model), _1: {ctor: '[]'} } }); @@ -16703,16 +16761,6 @@ var _user$project$Platformer$initialSocketCommand = function (flags) { var _user$project$Platformer$PhoenixMsg = function (a) { return {ctor: 'PhoenixMsg', _0: a}; }; -var _user$project$Platformer$init = function (flags) { - return { - ctor: '_Tuple2', - _0: _user$project$Platformer$initialModel(flags), - _1: A2( - _elm_lang$core$Platform_Cmd$map, - _user$project$Platformer$PhoenixMsg, - _user$project$Platformer$initialSocketCommand(flags)) - }; -}; var _user$project$Platformer$update = F2( function (msg, model) { var _p2 = msg; @@ -16891,6 +16939,25 @@ var _user$project$Platformer$fetchPlayersList = A2( _elm_lang$http$Http$send, _user$project$Platformer$FetchPlayersList, A2(_elm_lang$http$Http$get, '/api/players', _user$project$Platformer$decodePlayersList)); +var _user$project$Platformer$init = function (flags) { + return { + ctor: '_Tuple2', + _0: _user$project$Platformer$initialModel(flags), + _1: _elm_lang$core$Platform_Cmd$batch( + { + ctor: '::', + _0: _user$project$Platformer$fetchPlayersList, + _1: { + ctor: '::', + _0: A2( + _elm_lang$core$Platform_Cmd$map, + _user$project$Platformer$PhoenixMsg, + _user$project$Platformer$initialSocketCommand(flags)), + _1: {ctor: '[]'} + } + }) + }; +}; var _user$project$Platformer$CountdownTimer = function (a) { return {ctor: 'CountdownTimer', _0: a}; }; From 5c29d4389dedd3de0a38435c7e5ced316b738b2f Mon Sep 17 00:00:00 2001 From: Bijan Date: Wed, 3 Jan 2018 00:41:29 -0500 Subject: [PATCH 07/23] Fetch gameplays This works for rendering all the gameplays, but the http://0.0.0.0:4000/api/gameplays endpoint still needs to be fixed so that it includes the game_id and player_id. --- assets/elm/Platformer.elm | 40 +++++++++++----- assets/js/elm.js | 98 ++++++++++++++++++++++++++------------- 2 files changed, 95 insertions(+), 43 deletions(-) diff --git a/assets/elm/Platformer.elm b/assets/elm/Platformer.elm index 1d88106..0219b1d 100644 --- a/assets/elm/Platformer.elm +++ b/assets/elm/Platformer.elm @@ -140,7 +140,8 @@ init : Flags -> ( Model, Cmd Msg ) init flags = ( initialModel flags , Cmd.batch - [ fetchPlayersList + [ fetchGameplaysList + , fetchPlayersList , Cmd.map PhoenixMsg (initialSocketCommand flags) ] ) @@ -172,18 +173,24 @@ decodePlayer = (Decode.field "username" Decode.string) -type alias Score = - { gameId : Int - , playerId : Int - , playerScore : Int - } +fetchGameplaysList : Cmd Msg +fetchGameplaysList = + Http.get "/api/gameplays" decodeGameplaysList + |> Http.send FetchGameplaysList + + +decodeGameplaysList : Decode.Decoder (List Gameplay) +decodeGameplaysList = + decodeGameplay + |> Decode.list + |> Decode.at [ "data" ] -scoreDecoder : Decode.Decoder Score -scoreDecoder = - Decode.map3 Score - (Decode.field "game_id" Decode.int) - (Decode.field "player_id" Decode.int) +decodeGameplay : Decode.Decoder Gameplay +decodeGameplay = + Decode.map3 Gameplay + (Decode.field "id" Decode.int) + (Decode.field "id" Decode.int) (Decode.field "player_score" Decode.int) @@ -194,6 +201,7 @@ scoreDecoder = type Msg = NoOp | CountdownTimer Time + | FetchGameplaysList (Result Http.Error (List Gameplay)) | FetchPlayersList (Result Http.Error (List Player)) | KeyDown KeyCode | PhoenixMsg (Phoenix.Socket.Msg Msg) @@ -217,6 +225,14 @@ update msg model = else ( model, Cmd.none ) + FetchGameplaysList result -> + case result of + Ok gameplays -> + ( { model | gameplays = gameplays }, Cmd.none ) + + Err message -> + ( { model | errors = toString message }, Cmd.none ) + FetchPlayersList result -> case result of Ok players -> @@ -277,7 +293,7 @@ update msg model = ) ReceiveScoreChanges raw -> - case Decode.decodeValue scoreDecoder raw of + case Decode.decodeValue decodeGameplay raw of Ok scoreChange -> ( { model | gameplays = scoreChange :: model.gameplays }, Cmd.none ) diff --git a/assets/js/elm.js b/assets/js/elm.js index 56a0535..8cb4a74 100644 --- a/assets/js/elm.js +++ b/assets/js/elm.js @@ -16591,6 +16591,20 @@ var _user$project$Platformer$Gameplay = F3( function (a, b, c) { return {gameId: a, playerId: b, playerScore: c}; }); +var _user$project$Platformer$decodeGameplay = A4( + _elm_lang$core$Json_Decode$map3, + _user$project$Platformer$Gameplay, + A2(_elm_lang$core$Json_Decode$field, 'id', _elm_lang$core$Json_Decode$int), + A2(_elm_lang$core$Json_Decode$field, 'id', _elm_lang$core$Json_Decode$int), + A2(_elm_lang$core$Json_Decode$field, 'player_score', _elm_lang$core$Json_Decode$int)); +var _user$project$Platformer$decodeGameplaysList = A2( + _elm_lang$core$Json_Decode$at, + { + ctor: '::', + _0: 'data', + _1: {ctor: '[]'} + }, + _elm_lang$core$Json_Decode$list(_user$project$Platformer$decodeGameplay)); var _user$project$Platformer$Player = F4( function (a, b, c, d) { return {displayName: a, id: b, score: c, username: d}; @@ -16640,16 +16654,6 @@ var _user$project$Platformer$Model = function (a) { }; }; }; -var _user$project$Platformer$Score = F3( - function (a, b, c) { - return {gameId: a, playerId: b, playerScore: c}; - }); -var _user$project$Platformer$scoreDecoder = A4( - _elm_lang$core$Json_Decode$map3, - _user$project$Platformer$Score, - A2(_elm_lang$core$Json_Decode$field, 'game_id', _elm_lang$core$Json_Decode$int), - A2(_elm_lang$core$Json_Decode$field, 'player_id', _elm_lang$core$Json_Decode$int), - A2(_elm_lang$core$Json_Decode$field, 'player_score', _elm_lang$core$Json_Decode$int)); var _user$project$Platformer$Right = {ctor: 'Right'}; var _user$project$Platformer$Left = {ctor: 'Left'}; var _user$project$Platformer$GameOver = {ctor: 'GameOver'}; @@ -16775,14 +16779,14 @@ var _user$project$Platformer$update = F2( {timeRemaining: model.timeRemaining - 1}), _1: _elm_lang$core$Platform_Cmd$none } : {ctor: '_Tuple2', _0: model, _1: _elm_lang$core$Platform_Cmd$none}; - case 'FetchPlayersList': + case 'FetchGameplaysList': var _p3 = _p2._0; if (_p3.ctor === 'Ok') { return { ctor: '_Tuple2', _0: _elm_lang$core$Native_Utils.update( model, - {playersList: _p3._0}), + {gameplays: _p3._0}), _1: _elm_lang$core$Platform_Cmd$none }; } else { @@ -16796,9 +16800,30 @@ var _user$project$Platformer$update = F2( _1: _elm_lang$core$Platform_Cmd$none }; } - case 'KeyDown': + case 'FetchPlayersList': var _p4 = _p2._0; - switch (_p4) { + if (_p4.ctor === 'Ok') { + return { + ctor: '_Tuple2', + _0: _elm_lang$core$Native_Utils.update( + model, + {playersList: _p4._0}), + _1: _elm_lang$core$Platform_Cmd$none + }; + } else { + return { + ctor: '_Tuple2', + _0: _elm_lang$core$Native_Utils.update( + model, + { + errors: _elm_lang$core$Basics$toString(_p4._0) + }), + _1: _elm_lang$core$Platform_Cmd$none + }; + } + case 'KeyDown': + var _p5 = _p2._0; + switch (_p5) { case 32: return (!_elm_lang$core$Native_Utils.eq(model.gameState, _user$project$Platformer$Playing)) ? { ctor: '_Tuple2', @@ -16827,9 +16852,9 @@ var _user$project$Platformer$update = F2( return {ctor: '_Tuple2', _0: model, _1: _elm_lang$core$Platform_Cmd$none}; } case 'PhoenixMsg': - var _p5 = A2(_fbonetti$elm_phoenix_socket$Phoenix_Socket$update, _p2._0, model.phxSocket); - var phxSocket = _p5._0; - var phxCmd = _p5._1; + var _p6 = A2(_fbonetti$elm_phoenix_socket$Phoenix_Socket$update, _p2._0, model.phxSocket); + var phxSocket = _p6._0; + var phxCmd = _p6._1; return { ctor: '_Tuple2', _0: _elm_lang$core$Native_Utils.update( @@ -16838,14 +16863,14 @@ var _user$project$Platformer$update = F2( _1: A2(_elm_lang$core$Platform_Cmd$map, _user$project$Platformer$PhoenixMsg, phxCmd) }; case 'ReceiveScoreChanges': - var _p6 = A2(_elm_lang$core$Json_Decode$decodeValue, _user$project$Platformer$scoreDecoder, _p2._0); - if (_p6.ctor === 'Ok') { + var _p7 = A2(_elm_lang$core$Json_Decode$decodeValue, _user$project$Platformer$decodeGameplay, _p2._0); + if (_p7.ctor === 'Ok') { return { ctor: '_Tuple2', _0: _elm_lang$core$Native_Utils.update( model, { - gameplays: {ctor: '::', _0: _p6._0, _1: model.gameplays} + gameplays: {ctor: '::', _0: _p7._0, _1: model.gameplays} }), _1: _elm_lang$core$Platform_Cmd$none }; @@ -16854,7 +16879,7 @@ var _user$project$Platformer$update = F2( ctor: '_Tuple2', _0: _elm_lang$core$Native_Utils.update( model, - {errors: _p6._0}), + {errors: _p7._0}), _1: _elm_lang$core$Platform_Cmd$none }; } @@ -16886,9 +16911,9 @@ var _user$project$Platformer$update = F2( _fbonetti$elm_phoenix_socket$Phoenix_Push$withPayload, payload, A2(_fbonetti$elm_phoenix_socket$Phoenix_Push$init, 'save_score', 'score:platformer')))); - var _p7 = A2(_fbonetti$elm_phoenix_socket$Phoenix_Socket$push, phxPush, model.phxSocket); - var phxSocket = _p7._0; - var phxCmd = _p7._1; + var _p8 = A2(_fbonetti$elm_phoenix_socket$Phoenix_Socket$push, phxPush, model.phxSocket); + var phxSocket = _p8._0; + var phxCmd = _p8._1; return { ctor: '_Tuple2', _0: _elm_lang$core$Native_Utils.update( @@ -16939,6 +16964,13 @@ var _user$project$Platformer$fetchPlayersList = A2( _elm_lang$http$Http$send, _user$project$Platformer$FetchPlayersList, A2(_elm_lang$http$Http$get, '/api/players', _user$project$Platformer$decodePlayersList)); +var _user$project$Platformer$FetchGameplaysList = function (a) { + return {ctor: 'FetchGameplaysList', _0: a}; +}; +var _user$project$Platformer$fetchGameplaysList = A2( + _elm_lang$http$Http$send, + _user$project$Platformer$FetchGameplaysList, + A2(_elm_lang$http$Http$get, '/api/gameplays', _user$project$Platformer$decodeGameplaysList)); var _user$project$Platformer$init = function (flags) { return { ctor: '_Tuple2', @@ -16946,14 +16978,18 @@ var _user$project$Platformer$init = function (flags) { _1: _elm_lang$core$Platform_Cmd$batch( { ctor: '::', - _0: _user$project$Platformer$fetchPlayersList, + _0: _user$project$Platformer$fetchGameplaysList, _1: { ctor: '::', - _0: A2( - _elm_lang$core$Platform_Cmd$map, - _user$project$Platformer$PhoenixMsg, - _user$project$Platformer$initialSocketCommand(flags)), - _1: {ctor: '[]'} + _0: _user$project$Platformer$fetchPlayersList, + _1: { + ctor: '::', + _0: A2( + _elm_lang$core$Platform_Cmd$map, + _user$project$Platformer$PhoenixMsg, + _user$project$Platformer$initialSocketCommand(flags)), + _1: {ctor: '[]'} + } } }) }; @@ -16999,7 +17035,7 @@ if (typeof _user$project$Main$main !== 'undefined') { } Elm['Platformer'] = Elm['Platformer'] || {}; if (typeof _user$project$Platformer$main !== 'undefined') { - _user$project$Platformer$main(Elm['Platformer'], 'Platformer', {"types":{"unions":{"Dict.LeafColor":{"args":[],"tags":{"LBBlack":[],"LBlack":[]}},"Platformer.Msg":{"args":[],"tags":{"SetNewItemPositionX":["Int"],"SaveScoreRequest":[],"CountdownTimer":["Time.Time"],"FetchPlayersList":["Result.Result Http.Error (List Platformer.Player)"],"SaveScore":["Json.Encode.Value"],"PhoenixMsg":["Phoenix.Socket.Msg Platformer.Msg"],"TimeUpdate":["Time.Time"],"KeyDown":["Keyboard.KeyCode"],"ReceiveScoreChanges":["Json.Encode.Value"],"NoOp":[],"SaveScoreError":["Json.Encode.Value"]}},"Json.Encode.Value":{"args":[],"tags":{"Value":[]}},"Dict.Dict":{"args":["k","v"],"tags":{"RBNode_elm_builtin":["Dict.NColor","k","v","Dict.Dict k v","Dict.Dict k v"],"RBEmpty_elm_builtin":["Dict.LeafColor"]}},"Maybe.Maybe":{"args":["a"],"tags":{"Just":["a"],"Nothing":[]}},"Dict.NColor":{"args":[],"tags":{"BBlack":[],"Red":[],"NBlack":[],"Black":[]}},"Http.Error":{"args":[],"tags":{"BadUrl":["String"],"NetworkError":[],"Timeout":[],"BadStatus":["Http.Response String"],"BadPayload":["String","Http.Response String"]}},"Result.Result":{"args":["error","value"],"tags":{"Ok":["value"],"Err":["error"]}},"Phoenix.Socket.Msg":{"args":["msg"],"tags":{"ChannelErrored":["String"],"ChannelClosed":["String"],"ExternalMsg":["msg"],"ChannelJoined":["String"],"Heartbeat":["Time.Time"],"NoOp":[],"ReceiveReply":["String","Int"]}}},"aliases":{"Http.Response":{"args":["body"],"type":"{ url : String , status : { code : Int, message : String } , headers : Dict.Dict String String , body : body }"},"Keyboard.KeyCode":{"args":[],"type":"Int"},"Platformer.Player":{"args":[],"type":"{ displayName : Maybe.Maybe String , id : Int , score : Int , username : String }"},"Time.Time":{"args":[],"type":"Float"}},"message":"Platformer.Msg"},"versions":{"elm":"0.18.0"}}); + _user$project$Platformer$main(Elm['Platformer'], 'Platformer', {"types":{"unions":{"Dict.LeafColor":{"args":[],"tags":{"LBBlack":[],"LBlack":[]}},"Platformer.Msg":{"args":[],"tags":{"FetchGameplaysList":["Result.Result Http.Error (List Platformer.Gameplay)"],"SetNewItemPositionX":["Int"],"SaveScoreRequest":[],"CountdownTimer":["Time.Time"],"FetchPlayersList":["Result.Result Http.Error (List Platformer.Player)"],"SaveScore":["Json.Encode.Value"],"PhoenixMsg":["Phoenix.Socket.Msg Platformer.Msg"],"TimeUpdate":["Time.Time"],"KeyDown":["Keyboard.KeyCode"],"ReceiveScoreChanges":["Json.Encode.Value"],"NoOp":[],"SaveScoreError":["Json.Encode.Value"]}},"Json.Encode.Value":{"args":[],"tags":{"Value":[]}},"Dict.Dict":{"args":["k","v"],"tags":{"RBNode_elm_builtin":["Dict.NColor","k","v","Dict.Dict k v","Dict.Dict k v"],"RBEmpty_elm_builtin":["Dict.LeafColor"]}},"Maybe.Maybe":{"args":["a"],"tags":{"Just":["a"],"Nothing":[]}},"Dict.NColor":{"args":[],"tags":{"BBlack":[],"Red":[],"NBlack":[],"Black":[]}},"Http.Error":{"args":[],"tags":{"BadUrl":["String"],"NetworkError":[],"Timeout":[],"BadStatus":["Http.Response String"],"BadPayload":["String","Http.Response String"]}},"Result.Result":{"args":["error","value"],"tags":{"Ok":["value"],"Err":["error"]}},"Phoenix.Socket.Msg":{"args":["msg"],"tags":{"ChannelErrored":["String"],"ChannelClosed":["String"],"ExternalMsg":["msg"],"ChannelJoined":["String"],"Heartbeat":["Time.Time"],"NoOp":[],"ReceiveReply":["String","Int"]}}},"aliases":{"Http.Response":{"args":["body"],"type":"{ url : String , status : { code : Int, message : String } , headers : Dict.Dict String String , body : body }"},"Keyboard.KeyCode":{"args":[],"type":"Int"},"Platformer.Gameplay":{"args":[],"type":"{ gameId : Int, playerId : Int, playerScore : Int }"},"Platformer.Player":{"args":[],"type":"{ displayName : Maybe.Maybe String , id : Int , score : Int , username : String }"},"Time.Time":{"args":[],"type":"Float"}},"message":"Platformer.Msg"},"versions":{"elm":"0.18.0"}}); } if (typeof define === "function" && define['amd']) From f9dfcbe6508579f3fa3414699887fe3d52a492b0 Mon Sep 17 00:00:00 2001 From: Bijan Date: Wed, 3 Jan 2018 08:11:01 -0500 Subject: [PATCH 08/23] Link from home page to show player page --- assets/elm/Main.elm | 5 ++++- assets/js/elm.js | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/assets/elm/Main.elm b/assets/elm/Main.elm index 1993634..9ccac29 100644 --- a/assets/elm/Main.elm +++ b/assets/elm/Main.elm @@ -265,8 +265,11 @@ playersListItem player = player.username else Maybe.withDefault "" player.displayName + + playerLink = + "players/" ++ (toString player.id) in li [ class "player-item list-group-item" ] - [ strong [] [ text displayName ] + [ strong [] [ a [ href playerLink ] [ text displayName ] ] , span [ class "badge" ] [ text (toString player.score) ] ] diff --git a/assets/js/elm.js b/assets/js/elm.js index 8cb4a74..cca13ad 100644 --- a/assets/js/elm.js +++ b/assets/js/elm.js @@ -15476,6 +15476,10 @@ var _fbonetti$elm_phoenix_socket$Phoenix_Socket$listen = F2( }); var _user$project$Main$playersListItem = function (player) { + var playerLink = A2( + _elm_lang$core$Basics_ops['++'], + 'players/', + _elm_lang$core$Basics$toString(player.id)); var displayName = _elm_lang$core$Native_Utils.eq(player.displayName, _elm_lang$core$Maybe$Nothing) ? player.username : A2(_elm_lang$core$Maybe$withDefault, '', player.displayName); return A2( _elm_lang$html$Html$li, @@ -15491,7 +15495,18 @@ var _user$project$Main$playersListItem = function (player) { {ctor: '[]'}, { ctor: '::', - _0: _elm_lang$html$Html$text(displayName), + _0: A2( + _elm_lang$html$Html$a, + { + ctor: '::', + _0: _elm_lang$html$Html_Attributes$href(playerLink), + _1: {ctor: '[]'} + }, + { + ctor: '::', + _0: _elm_lang$html$Html$text(displayName), + _1: {ctor: '[]'} + }), _1: {ctor: '[]'} }), _1: { From c9d06c0d7d642783ea1d1d85eeae4f77991b2b1d Mon Sep 17 00:00:00 2001 From: Bijan Date: Wed, 3 Jan 2018 08:32:13 -0500 Subject: [PATCH 09/23] Render gameplays on player show page --- lib/platform/accounts/accounts.ex | 6 ++- lib/platform/accounts/player.ex | 3 +- lib/platform/products/game.ex | 3 +- .../templates/player/show.html.eex | 41 +++++++++++++++---- lib/platform_web/views/player_view.ex | 13 ++++++ 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/lib/platform/accounts/accounts.ex b/lib/platform/accounts/accounts.ex index fc105fe..09793dd 100644 --- a/lib/platform/accounts/accounts.ex +++ b/lib/platform/accounts/accounts.ex @@ -35,7 +35,11 @@ defmodule Platform.Accounts do ** (Ecto.NoResultsError) """ - def get_player!(id), do: Repo.get!(Player, id) + def get_player!(id) do + Player + |> preload([:gameplays]) + |> Repo.get!(id) + end @doc """ Creates a player. diff --git a/lib/platform/accounts/player.ex b/lib/platform/accounts/player.ex index 51df7a3..d214724 100644 --- a/lib/platform/accounts/player.ex +++ b/lib/platform/accounts/player.ex @@ -2,12 +2,11 @@ defmodule Platform.Accounts.Player do use Ecto.Schema import Ecto.Changeset alias Platform.Accounts.Player - alias Platform.Products.Game alias Platform.Products.Gameplay schema "players" do - many_to_many :games, Game, join_through: Gameplay + has_many :gameplays, Gameplay field :display_name, :string field :password, :string, virtual: true diff --git a/lib/platform/products/game.ex b/lib/platform/products/game.ex index e49a546..2500344 100644 --- a/lib/platform/products/game.ex +++ b/lib/platform/products/game.ex @@ -3,11 +3,10 @@ defmodule Platform.Products.Game do import Ecto.Changeset alias Platform.Products.Game alias Platform.Products.Gameplay - alias Platform.Accounts.Player schema "games" do - many_to_many :players, Player, join_through: Gameplay + has_many :gameplays, Gameplay field :description, :string field :featured, :boolean, default: false diff --git a/lib/platform_web/templates/player/show.html.eex b/lib/platform_web/templates/player/show.html.eex index a1359ee..6f49e0a 100644 --- a/lib/platform_web/templates/player/show.html.eex +++ b/lib/platform_web/templates/player/show.html.eex @@ -1,11 +1,36 @@ -

Show Player

- -
    -
  • ID: <%= @player.id %>
  • -
  • Display Name: <%= @player.display_name %>
  • -
  • Username: <%= @player.username %>
  • -
  • Score: <%= @player.score %>
  • -
+
+

Show Player

+ +
    +
  • ID: <%= @player.id %>
  • +
  • Display Name: <%= @player.display_name %>
  • +
  • Username: <%= @player.username %>
  • +
  • Score: <%= @player.score %>
  • +
+
+ +
+

Player Scores

+ + + + + + + + + + + <%= for gameplay <- @player.gameplays do %> + + + + + + <% end %> + +
Game IDPlayer IDPlayer Score
<%= game_title(gameplay.game_id) %><%= player_name(gameplay.player_id) %><%= gameplay.player_score %>
+
<%= link "Edit", to: player_path(@conn, :edit, @player), class: "btn btn-default" %> <%= link "Back", to: page_path(@conn, :index), class: "btn btn-default" %> diff --git a/lib/platform_web/views/player_view.ex b/lib/platform_web/views/player_view.ex index 0b5b004..15d2d16 100644 --- a/lib/platform_web/views/player_view.ex +++ b/lib/platform_web/views/player_view.ex @@ -1,3 +1,16 @@ defmodule PlatformWeb.PlayerView do use PlatformWeb, :view + + alias Platform.Accounts + alias Platform.Products + + def game_title(game_id) do + game = Products.get_game!(game_id) + game.title + end + + def player_name(id) do + player = Accounts.get_player!(id) + if player.display_name != nil, do: player.display_name, else: player.username + end end From ca618e6ff95fe40baf96fbfa4b4b2a3228705ccf Mon Sep 17 00:00:00 2001 From: Bijan Date: Wed, 3 Jan 2018 08:49:55 -0500 Subject: [PATCH 10/23] Update seeds --- priv/repo/seeds.exs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index e52ce83..d57bd1a 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -17,10 +17,10 @@ alias Platform.Products # Players -Accounts.create_player(%{display_name: "José Valim", username: "josevalim", password: "josevalim", score: 1000}) -Accounts.create_player(%{display_name: "Evan Czaplicki", username: "evancz", password: "evancz", score: 2000}) -Accounts.create_player(%{display_name: "Chris McCord", username: "chrismccord", password: "chrismccord", score: 3000}) +Accounts.create_player(%{display_name: "José Valim", username: "josevalim", password: "josevalim", score: 0}) +Accounts.create_player(%{display_name: "Evan Czaplicki", username: "evancz", password: "evancz", score: 0}) +Accounts.create_player(%{display_name: "Chris McCord", username: "chrismccord", password: "chrismccord", score: 0}) # Games -Products.create_game(%{title: "Platformer", slug: "platformer", description: "Platform game example.", thumbnail: "http://via.placeholder.com/300x200", featured: true}) +Products.create_game(%{title: "Platformer", slug: "platformer", description: "Platform game example.", thumbnail: "https://i.imgur.com/L6ci0xL.png", featured: true}) From 46099f7b5693e762b1584544a5f479b520bf9904 Mon Sep 17 00:00:00 2001 From: Bijan Date: Wed, 3 Jan 2018 08:50:11 -0500 Subject: [PATCH 11/23] Fix gameplay decoder --- assets/elm/Platformer.elm | 19 ++---------------- assets/js/elm.js | 42 ++------------------------------------- 2 files changed, 4 insertions(+), 57 deletions(-) diff --git a/assets/elm/Platformer.elm b/assets/elm/Platformer.elm index 0219b1d..202cfbc 100644 --- a/assets/elm/Platformer.elm +++ b/assets/elm/Platformer.elm @@ -189,8 +189,8 @@ decodeGameplaysList = decodeGameplay : Decode.Decoder Gameplay decodeGameplay = Decode.map3 Gameplay - (Decode.field "id" Decode.int) - (Decode.field "id" Decode.int) + (Decode.field "game_id" Decode.int) + (Decode.field "player_id" Decode.int) (Decode.field "player_score" Decode.int) @@ -443,21 +443,6 @@ viewPlayerScoreItem model gameplay = ] -playersListItem : Player -> Html msg -playersListItem player = - let - displayName = - if player.displayName == Nothing then - player.username - else - Maybe.withDefault "" player.displayName - in - li [ Html.Attributes.class "player-item list-group-item" ] - [ strong [] [ text displayName ] - , span [ Html.Attributes.class "badge" ] [ text (toString player.score) ] - ] - - viewGame : Model -> Svg Msg viewGame model = svg [ version "1.1", width "600", height "400" ] diff --git a/assets/js/elm.js b/assets/js/elm.js index cca13ad..bc02df0 100644 --- a/assets/js/elm.js +++ b/assets/js/elm.js @@ -16428,44 +16428,6 @@ var _user$project$Platformer$viewGame = function (model) { }, _user$project$Platformer$viewGameState(model)); }; -var _user$project$Platformer$playersListItem = function (player) { - var displayName = _elm_lang$core$Native_Utils.eq(player.displayName, _elm_lang$core$Maybe$Nothing) ? player.username : A2(_elm_lang$core$Maybe$withDefault, '', player.displayName); - return A2( - _elm_lang$html$Html$li, - { - ctor: '::', - _0: _elm_lang$html$Html_Attributes$class('player-item list-group-item'), - _1: {ctor: '[]'} - }, - { - ctor: '::', - _0: A2( - _elm_lang$html$Html$strong, - {ctor: '[]'}, - { - ctor: '::', - _0: _elm_lang$svg$Svg$text(displayName), - _1: {ctor: '[]'} - }), - _1: { - ctor: '::', - _0: A2( - _elm_lang$html$Html$span, - { - ctor: '::', - _0: _elm_lang$html$Html_Attributes$class('badge'), - _1: {ctor: '[]'} - }, - { - ctor: '::', - _0: _elm_lang$svg$Svg$text( - _elm_lang$core$Basics$toString(player.score)), - _1: {ctor: '[]'} - }), - _1: {ctor: '[]'} - } - }); -}; var _user$project$Platformer$anonymousPlayer = { displayName: _elm_lang$core$Maybe$Just('Anonymous User'), id: 0, @@ -16609,8 +16571,8 @@ var _user$project$Platformer$Gameplay = F3( var _user$project$Platformer$decodeGameplay = A4( _elm_lang$core$Json_Decode$map3, _user$project$Platformer$Gameplay, - A2(_elm_lang$core$Json_Decode$field, 'id', _elm_lang$core$Json_Decode$int), - A2(_elm_lang$core$Json_Decode$field, 'id', _elm_lang$core$Json_Decode$int), + A2(_elm_lang$core$Json_Decode$field, 'game_id', _elm_lang$core$Json_Decode$int), + A2(_elm_lang$core$Json_Decode$field, 'player_id', _elm_lang$core$Json_Decode$int), A2(_elm_lang$core$Json_Decode$field, 'player_score', _elm_lang$core$Json_Decode$int)); var _user$project$Platformer$decodeGameplaysList = A2( _elm_lang$core$Json_Decode$at, From 428a585a44a15b560ff93cbb6922dd58dd5f0ddc Mon Sep 17 00:00:00 2001 From: Bijan Date: Wed, 3 Jan 2018 08:53:37 -0500 Subject: [PATCH 12/23] Guard against empty player scores section --- lib/platform_web/templates/player/show.html.eex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/platform_web/templates/player/show.html.eex b/lib/platform_web/templates/player/show.html.eex index 6f49e0a..36dd043 100644 --- a/lib/platform_web/templates/player/show.html.eex +++ b/lib/platform_web/templates/player/show.html.eex @@ -9,6 +9,7 @@ +<%= if Enum.count(@player.gameplays) > 0 do %>

Player Scores

@@ -31,6 +32,7 @@
+<% end %> <%= link "Edit", to: player_path(@conn, :edit, @player), class: "btn btn-default" %> <%= link "Back", to: page_path(@conn, :index), class: "btn btn-default" %> From 0dfff1ab73025a740b6a0f712f258f4ad19f25b0 Mon Sep 17 00:00:00 2001 From: Bijan Date: Sat, 13 Jan 2018 05:11:59 -0500 Subject: [PATCH 13/23] Include gameplay data for JSON endpoint --- lib/platform_web/views/gameplay_view.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/platform_web/views/gameplay_view.ex b/lib/platform_web/views/gameplay_view.ex index 9c4cdf7..429b509 100644 --- a/lib/platform_web/views/gameplay_view.ex +++ b/lib/platform_web/views/gameplay_view.ex @@ -12,6 +12,8 @@ defmodule PlatformWeb.GameplayView do def render("gameplay.json", %{gameplay: gameplay}) do %{id: gameplay.id, + game_id: gameplay.game_id, + player_id: gameplay.player_id, player_score: gameplay.player_score} end end From 9c3fb08eb3f145865a22dad5bd0cc5cd9c2707eb Mon Sep 17 00:00:00 2001 From: Bijan Date: Sat, 13 Jan 2018 05:12:45 -0500 Subject: [PATCH 14/23] Updates for player show template --- lib/platform_web/templates/player/show.html.eex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/platform_web/templates/player/show.html.eex b/lib/platform_web/templates/player/show.html.eex index 36dd043..7e93f25 100644 --- a/lib/platform_web/templates/player/show.html.eex +++ b/lib/platform_web/templates/player/show.html.eex @@ -1,22 +1,22 @@ -
+

Show Player

  • ID: <%= @player.id %>
  • Display Name: <%= @player.display_name %>
  • Username: <%= @player.username %>
  • -
  • Score: <%= @player.score %>
  • +
  • Total Score: <%= @player.score %>
<%= if Enum.count(@player.gameplays) > 0 do %> -
+

Player Scores

- + From d687939a25d8beafb908a6ef754feb73c6f6f9f9 Mon Sep 17 00:00:00 2001 From: Bijan Date: Sat, 13 Jan 2018 08:59:05 -0500 Subject: [PATCH 15/23] Preload gameplays for players and games --- lib/platform/accounts/accounts.ex | 2 +- lib/platform/products/products.ex | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/platform/accounts/accounts.ex b/lib/platform/accounts/accounts.ex index 09793dd..4c7e4c6 100644 --- a/lib/platform/accounts/accounts.ex +++ b/lib/platform/accounts/accounts.ex @@ -37,7 +37,7 @@ defmodule Platform.Accounts do """ def get_player!(id) do Player - |> preload([:gameplays]) + |> preload(:gameplays) |> Repo.get!(id) end diff --git a/lib/platform/products/products.ex b/lib/platform/products/products.ex index f7a365c..64c870b 100644 --- a/lib/platform/products/products.ex +++ b/lib/platform/products/products.ex @@ -37,8 +37,15 @@ defmodule Platform.Products do ** (Ecto.NoResultsError) """ - def get_game!(id), do: Repo.get!(Game, id) - def get_game_by_slug!(slug), do: Repo.get_by!(Game, slug: slug) + def get_game!(id) do + Game + |> preload(:gameplays) + |> Repo.get!(id) + end + + def get_game_by_slug!(slug) do + Repo.get_by!(Game, slug: slug) + end @doc """ Creates a game. From a7501715aac10e0cdd5d288051579b06cab2e933 Mon Sep 17 00:00:00 2001 From: Bijan Date: Sat, 13 Jan 2018 09:36:25 -0500 Subject: [PATCH 16/23] Add ex_machina dependency for tests --- mix.exs | 3 ++- mix.lock | 1 + test/support/factory.ex | 22 ++++++++++++++++++++++ test/test_helper.exs | 2 +- 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 test/support/factory.ex diff --git a/mix.exs b/mix.exs index e0dc4ef..4bc7060 100644 --- a/mix.exs +++ b/mix.exs @@ -42,7 +42,8 @@ defmodule Platform.Mixfile do {:gettext, "~> 0.11"}, {:cowboy, "~> 1.0"}, {:comeonin, "~> 4.0"}, - {:bcrypt_elixir, "~> 0.12"} + {:bcrypt_elixir, "~> 0.12"}, + {:ex_machina, "~> 2.1", only: :test}, ] end diff --git a/mix.lock b/mix.lock index 71d59c3..778b959 100644 --- a/mix.lock +++ b/mix.lock @@ -7,6 +7,7 @@ "decimal": {:hex, :decimal, "1.4.0", "fac965ce71a46aab53d3a6ce45662806bdd708a4a95a65cde8a12eb0124a1333", [], [], "hexpm"}, "ecto": {:hex, :ecto, "2.1.6", "29b45f393c2ecd99f83e418ea9b0a2af6078ecb30f401481abac8a473c490f84", [], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, "elixir_make": {:hex, :elixir_make, "0.4.0", "992f38fabe705bb45821a728f20914c554b276838433349d4f2341f7a687cddf", [], [], "hexpm"}, + "ex_machina": {:hex, :ex_machina, "2.1.0", "4874dc9c78e7cf2d429f24dc3c4005674d4e4da6a08be961ffccc08fb528e28b", [], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [], [], "hexpm"}, "gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [], [], "hexpm"}, "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [], [], "hexpm"}, diff --git a/test/support/factory.ex b/test/support/factory.ex new file mode 100644 index 0000000..ee1c283 --- /dev/null +++ b/test/support/factory.ex @@ -0,0 +1,22 @@ +defmodule Platform.Factory do + use ExMachina.Ecto, repo: Platform.Repo + + def player_factory do + %Platform.Accounts.Player{ + display_name: "José Valim", + username: "josevalim", + password: "josevalim", + score: 0, + } + end + + def game_factory do + %Platform.Products.Game{ + description: "Platformer game example.", + featured: true, + slug: "platformer", + thumbnail: "https://i.imgur.com/L6ci0xL.png", + title: "Platformer", + } + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index ce0dee4..0bd9465 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,4 @@ ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(Platform.Repo, :manual) - +{:ok, _} = Application.ensure_all_started(:ex_machina) From 60a1a56ec764568075a62a48bb23ad3d91326db6 Mon Sep 17 00:00:00 2001 From: Bijan Date: Sat, 13 Jan 2018 09:45:57 -0500 Subject: [PATCH 17/23] Replace player fixtures with factories --- test/platform/accounts/accounts_test.exs | 14 ++++++++------ test/platform/products/products_test.exs | 1 + test/support/factory.ex | 1 - test/test_helper.exs | 1 + 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/test/platform/accounts/accounts_test.exs b/test/platform/accounts/accounts_test.exs index 3c9aa9e..9a1a5a8 100644 --- a/test/platform/accounts/accounts_test.exs +++ b/test/platform/accounts/accounts_test.exs @@ -2,6 +2,7 @@ defmodule Platform.AccountsTest do use Platform.DataCase alias Platform.Accounts + import Platform.Factory describe "players" do alias Platform.Accounts.Player @@ -25,13 +26,14 @@ defmodule Platform.AccountsTest do |> Map.merge(player_attrs_map) end + @tag :hey test "list_players/0 returns all players" do - player = player_fixture() + player = insert(:player) assert Accounts.list_players() == [player] end test "get_player!/1 returns the player with given id" do - player = player_fixture() + player = insert(:player) assert Accounts.get_player!(player.id) == player end @@ -46,7 +48,7 @@ defmodule Platform.AccountsTest do end test "update_player/2 with valid data updates the player" do - player = player_fixture() + player = insert(:player) assert {:ok, player} = Accounts.update_player(player, @update_attrs) assert %Player{} = player assert player.score == 43 @@ -54,19 +56,19 @@ defmodule Platform.AccountsTest do end test "update_player/2 with invalid data returns error changeset" do - player = player_fixture() + player = insert(:player) assert {:error, %Ecto.Changeset{}} = Accounts.update_player(player, @invalid_attrs) assert player == Accounts.get_player!(player.id) end test "delete_player/1 deletes the player" do - player = player_fixture() + player = insert(:player) assert {:ok, %Player{}} = Accounts.delete_player(player) assert_raise Ecto.NoResultsError, fn -> Accounts.get_player!(player.id) end end test "change_player/1 returns a player changeset" do - player = player_fixture() + player = insert(:player) assert %Ecto.Changeset{} = Accounts.change_player(player) end end diff --git a/test/platform/products/products_test.exs b/test/platform/products/products_test.exs index 086088f..e497eff 100644 --- a/test/platform/products/products_test.exs +++ b/test/platform/products/products_test.exs @@ -2,6 +2,7 @@ defmodule Platform.ProductsTest do use Platform.DataCase alias Platform.Products + import Platform.Factory describe "games" do alias Platform.Products.Game diff --git a/test/support/factory.ex b/test/support/factory.ex index ee1c283..0ca8f83 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -5,7 +5,6 @@ defmodule Platform.Factory do %Platform.Accounts.Player{ display_name: "José Valim", username: "josevalim", - password: "josevalim", score: 0, } end diff --git a/test/test_helper.exs b/test/test_helper.exs index 0bd9465..d319922 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,5 @@ ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(Platform.Repo, :manual) + {:ok, _} = Application.ensure_all_started(:ex_machina) From 7debd41b53b5ccc8430607a6a223f056765477c5 Mon Sep 17 00:00:00 2001 From: Bijan Date: Sat, 13 Jan 2018 12:28:58 -0500 Subject: [PATCH 18/23] Tinkering with Ecto relationships Got stuck pretty bad here for a while. The good news is it seems like a good start to be able to query for things like `player.games` and `player.gameplays` and `game.players` and `game.gameplays`. The schemas seem okay, but I was having a tough time figuring out how `cast_assoc`, `build_assoc`, and `put_assoc` work. In the current state, it looks like players and games are properly linked, but the gameplays get created without loading the associations properly. --- lib/platform/accounts/player.ex | 4 ++++ lib/platform/products/game.ex | 4 ++++ lib/platform/products/gameplay.ex | 4 ++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/platform/accounts/player.ex b/lib/platform/accounts/player.ex index d214724..fcbe6b4 100644 --- a/lib/platform/accounts/player.ex +++ b/lib/platform/accounts/player.ex @@ -2,10 +2,12 @@ defmodule Platform.Accounts.Player do use Ecto.Schema import Ecto.Changeset alias Platform.Accounts.Player + alias Platform.Products.Game alias Platform.Products.Gameplay schema "players" do + many_to_many :games, Game, join_through: Gameplay has_many :gameplays, Gameplay field :display_name, :string @@ -20,6 +22,8 @@ defmodule Platform.Accounts.Player do @doc false def changeset(%Player{} = player, attrs) do player + |> cast_assoc(:games) + |> Ecto.build_assoc(:gameplays) |> cast(attrs, [:display_name, :password, :score, :username]) |> validate_required([:username]) |> unique_constraint(:username) diff --git a/lib/platform/products/game.ex b/lib/platform/products/game.ex index 2500344..b4573eb 100644 --- a/lib/platform/products/game.ex +++ b/lib/platform/products/game.ex @@ -1,11 +1,13 @@ defmodule Platform.Products.Game do use Ecto.Schema import Ecto.Changeset + alias Platform.Accounts.Player alias Platform.Products.Game alias Platform.Products.Gameplay schema "games" do + many_to_many :players, Player, join_through: Gameplay has_many :gameplays, Gameplay field :description, :string @@ -20,6 +22,8 @@ defmodule Platform.Products.Game do @doc false def changeset(%Game{} = game, attrs) do game + |> cast_assoc(:players) + |> Ecto.build_assoc(:gameplays) |> cast(attrs, [:description, :featured, :slug, :thumbnail, :title]) |> validate_required([:description, :featured, :slug, :thumbnail, :title]) |> unique_constraint(:slug) diff --git a/lib/platform/products/gameplay.ex b/lib/platform/products/gameplay.ex index afce1c0..6c085b9 100644 --- a/lib/platform/products/gameplay.ex +++ b/lib/platform/products/gameplay.ex @@ -17,9 +17,9 @@ defmodule Platform.Products.Gameplay do @doc false def changeset(%Gameplay{} = gameplay, attrs) do gameplay + # |> cast_assoc(:game) + # |> cast_assoc(:player) |> cast(attrs, [:game_id, :player_id, :player_score]) |> validate_required([:game_id, :player_id, :player_score]) - |> foreign_key_constraint(:game_id) - |> foreign_key_constraint(:player_id) end end From 891a5bc67bd4c4542dd9a40470b6830d04081e69 Mon Sep 17 00:00:00 2001 From: Bijan Date: Sat, 13 Jan 2018 12:32:38 -0500 Subject: [PATCH 19/23] Add preload queries for players and games --- lib/platform/accounts/accounts.ex | 6 +++++- lib/platform/products/products.ex | 10 +++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/platform/accounts/accounts.ex b/lib/platform/accounts/accounts.ex index 4c7e4c6..cc6d222 100644 --- a/lib/platform/accounts/accounts.ex +++ b/lib/platform/accounts/accounts.ex @@ -18,7 +18,10 @@ defmodule Platform.Accounts do """ def list_players do - Repo.all(Player) + Player + |> preload(:games) + |> preload(:gameplays) + |> Repo.all() end @doc """ @@ -37,6 +40,7 @@ defmodule Platform.Accounts do """ def get_player!(id) do Player + |> preload(:games) |> preload(:gameplays) |> Repo.get!(id) end diff --git a/lib/platform/products/products.ex b/lib/platform/products/products.ex index 64c870b..2386a93 100644 --- a/lib/platform/products/products.ex +++ b/lib/platform/products/products.ex @@ -20,7 +20,10 @@ defmodule Platform.Products do """ def list_games do - Repo.all(Game) + Game + |> preload(:players) + |> preload(:gameplays) + |> Repo.all() end @doc """ @@ -39,6 +42,7 @@ defmodule Platform.Products do """ def get_game!(id) do Game + |> preload(:players) |> preload(:gameplays) |> Repo.get!(id) end @@ -160,8 +164,8 @@ defmodule Platform.Products do """ def create_gameplay(attrs \\ %{}) do # Update player total score. - player = Accounts.get_player!(attrs[:player_id]) - Accounts.update_player(player, %{score: player.score + attrs[:player_score]}) + # player = Accounts.get_player!(attrs[:player_id]) + # Accounts.update_player(player, %{score: player.score + attrs[:player_score]}) # Create gameplay record. %Gameplay{} From 2300565ddbe650e55247a13520661b7c8213a114 Mon Sep 17 00:00:00 2001 From: Bijan Date: Sat, 13 Jan 2018 12:54:54 -0500 Subject: [PATCH 20/23] Preload games and players for gameplays --- lib/platform/products/products.ex | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/platform/products/products.ex b/lib/platform/products/products.ex index 2386a93..dbc1140 100644 --- a/lib/platform/products/products.ex +++ b/lib/platform/products/products.ex @@ -127,7 +127,10 @@ defmodule Platform.Products do """ def list_gameplays do - Repo.all(Gameplay) + Gameplay + |> preload(:game) + |> preload(:player) + |> Repo.all() end @doc """ @@ -144,7 +147,13 @@ defmodule Platform.Products do ** (Ecto.NoResultsError) """ - def get_gameplay!(id), do: Repo.get!(Gameplay, id) + def get_gameplay!(id) do + Gameplay + |> preload(:game) + |> preload(:player) + |> Repo.get!(id) + end + def get_gameplays_by_id!(id) do query = from gp in "gameplays", where: gp.game_id == ^id, select: [:player_id, :player_score] Repo.all(query) From fd3b7ad175263ef7af71eddc6d6a476b3700665f Mon Sep 17 00:00:00 2001 From: Bijan Date: Sat, 13 Jan 2018 13:29:06 -0500 Subject: [PATCH 21/23] Store ids in gameplay records --- lib/platform/products/products.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/platform/products/products.ex b/lib/platform/products/products.ex index dbc1140..f6a0db9 100644 --- a/lib/platform/products/products.ex +++ b/lib/platform/products/products.ex @@ -172,6 +172,8 @@ defmodule Platform.Products do """ def create_gameplay(attrs \\ %{}) do + attrs = Enum.into(attrs, %{game_id: attrs[:game].id, player_id: attrs[:player].id}) + # Update player total score. # player = Accounts.get_player!(attrs[:player_id]) # Accounts.update_player(player, %{score: player.score + attrs[:player_score]}) From ecc2800df5a1a4b7fed00acb3802a1dea37dfb8f Mon Sep 17 00:00:00 2001 From: Bijan Date: Sat, 13 Jan 2018 13:50:12 -0500 Subject: [PATCH 22/23] Start updating factories --- test/support/factory.ex | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/support/factory.ex b/test/support/factory.ex index 0ca8f83..81930ba 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -4,6 +4,8 @@ defmodule Platform.Factory do def player_factory do %Platform.Accounts.Player{ display_name: "José Valim", + games: [], + gameplays: [], username: "josevalim", score: 0, } @@ -13,9 +15,19 @@ defmodule Platform.Factory do %Platform.Products.Game{ description: "Platformer game example.", featured: true, - slug: "platformer", + gameplays: [], + players: [], + slug: Enum.random(0..1000) |> Integer.to_string, thumbnail: "https://i.imgur.com/L6ci0xL.png", title: "Platformer", } end + + def gameplay_factory do + %Platform.Products.Gameplay{ + game: build(:game), + player: build(:player), + player_score: 666 + } + end end From 9c0259bb48f020dd27c7b815041568cc118ccd8f Mon Sep 17 00:00:00 2001 From: Bijan Date: Sat, 13 Jan 2018 13:50:45 -0500 Subject: [PATCH 23/23] Remove unused fixture --- test/platform/accounts/accounts_test.exs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/platform/accounts/accounts_test.exs b/test/platform/accounts/accounts_test.exs index 9a1a5a8..97f5e29 100644 --- a/test/platform/accounts/accounts_test.exs +++ b/test/platform/accounts/accounts_test.exs @@ -11,22 +11,6 @@ defmodule Platform.AccountsTest do @update_attrs %{display_name: "some updated display name", password: "some updated password", score: 43, username: "some updated username"} @invalid_attrs %{password: nil, username: nil} - def player_fixture(attrs \\ %{}) do - {:ok, player} = - attrs - |> Enum.into(@valid_attrs) - |> Accounts.create_player() - - player_attrs_map = - player - |> Map.from_struct() - |> Map.delete(:password) - - %Platform.Accounts.Player{} - |> Map.merge(player_attrs_map) - end - - @tag :hey test "list_players/0 returns all players" do player = insert(:player) assert Accounts.list_players() == [player]
Game IDGame Played Player ID Player Score