https://raw.githubusercontent.com/ajmaradiaga/feeds/main/scmt/members/developer-advocates/ajay_soreng.xml SAP Community - Ajay Soreng 2026-07-24T11:30:01.443665+00:00 python-feedgen Ajay Soreng in SAP Community https://community.sap.com/t5/devtoberfest/cap-tools-what-s-new-and-hot/ev-p/14214789 ๐ŸŸ  CAP Tools โ€“ What's New and Hot 2025-09-11T15:30:58.954000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P><SPAN>Learn what's hot and new in CAP tools, like<BR /><BR /></SPAN><SPAN>- </SPAN><SPAN>IntelliJ IDEs support<BR /></SPAN><SPAN>- </SPAN><SPAN>Linter checks for custom code<BR /></SPAN><SPAN>- </SPAN><SPAN>CLI enhancements for debugging, deployment, repl, and more</SPAN></P><P>SPEAKER:&nbsp;Christian Georgi</P><P>&nbsp;</P><DIV class=""><P><SPAN>We hope you enjoyed exploring how CAP&nbsp;Devtoberfest session "CAP Tools โ€“ What's New and Hot"</SPAN></P><P><SPAN>Please find the Devtoberfest validation tutorial for the CAP track, Week 3</SPAN></P><P><SPAN>Devtoberfest validation tutorial question will be available by EOD after the 4rd CAP Devtoberfest session today.</SPAN></P><P><SPAN>You can find it here :&nbsp;<A href="https://developers.sap.com/tutorials/devtoberfest2025-cap-week3-session-validation.html" target="_blank" rel="noopener noreferrer">https://developers.sap.com/tutorials/devtoberfest2025-cap-week3-session-validation.html</A></SPAN></P></DIV><DIV class="">&nbsp;</DIV> 2025-09-11T15:30:58.954000+02:00 https://community.sap.com/t5/devtoberfest/best-practices-in-cap/ev-p/14215088 ๐ŸŸ  Best Practices in CAP 2025-09-11T20:45:01.235000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P>In this session Daniel Hutzel - CPO and founder of CAP - will explain how to use CAP. And how to not use it... <span class="lia-unicode-emoji" title=":slightly_smiling_face:">๐Ÿ™‚</span></P><DIV class=""><P>&nbsp;</P><P><SPAN>We hope you enjoyed exploring how CAP&nbsp;Devtoberfest session "Best Practices in CAP"</SPAN></P><P><SPAN>Please find the Devtoberfest validation tutorial for the CAP track, Week 3</SPAN></P><P><SPAN>Devtoberfest validation tutorial question will be available by EOD after the 4rd CAP Devtoberfest session today.</SPAN></P><P><SPAN>You can find it here :&nbsp;<A href="https://developers.sap.com/tutorials/devtoberfest2025-cap-week3-session-validation.html" target="_blank" rel="noopener noreferrer">https://developers.sap.com/tutorials/devtoberfest2025-cap-week3-session-validation.html</A></SPAN></P></DIV><DIV class="">&nbsp;</DIV> 2025-09-11T20:45:01.235000+02:00 https://community.sap.com/t5/sap-codejam-discussions/codejam-request-pune-q1-2026-build-ai-services-using-the-sap-cloud/m-p/14218170#M883 Re: CodeJam Request | Pune| Q1 2026- Build AI services using the SAP Cloud Application Programming M 2025-09-16T07:42:04.989000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P>Hi Ashutosh,&nbsp;</P><P>Thanks for the request. We will plan for the CodeJam in Q1 2026 for Pune.&nbsp;</P><P>Thanks and Regards, Ajay</P> 2025-09-16T07:42:04.989000+02:00 https://community.sap.com/t5/sap-codejam-discussions/sap-codejam-request-pune-india-may-2026/m-p/14349554#M936 Re: SAP CodeJam Request - Pune, India - May 2026 2026-03-16T08:12:12.191000+01:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/874494">@max_vandam</a>&nbsp;Thanks for the request. I will discuss further for the planning in the mail.&nbsp;</P> 2026-03-16T08:12:12.191000+01:00 https://community.sap.com/t5/sap-codejam-discussions/request-to-host-sap-codejam-nagarro-gurgaon/m-p/14361143#M939 Re: Request to Host SAP CodeJam โ€“ Nagarro, Gurgaon 2026-03-30T08:52:05.590000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P>Hi Ashutosh,&nbsp;&nbsp;</P><P>I have sent you the meeting invite for the planning discussion.</P><P>&nbsp;</P><P>&nbsp;</P> 2026-03-30T08:52:05.590000+02:00 https://community.sap.com/t5/bengaluru-blog-posts/sap-cloud-application-programming-model-with-sap-hana-cloud-at-tarento/ba-p/13859435 SAP Cloud Application Programming Model with SAP HANA Cloud at Tarento Technologies - 6th September 2026-04-02T15:17:06.610000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P>It was an eventful day at SAP CodeJam kindly hosted by&nbsp;<SPAN>Tarento Technologies India in Bengaluru.&nbsp;</SPAN></P><P><SPAN>We thank the participants for taking a day out to spend it doing the CodeJam exercises. It was a great sight seeing the SAP Developer Community coming together, learning and networking. We had some pretty tough technical infrastructure related challenges. But it was a great team collaboration where collectively the group met the days goals in the end. Kudos to all the participants. Special thanks to&nbsp;<a href="https://community.sap.com/t5/user/viewprofilepage/user-id/8864">@SumanthVedantam27</a>&nbsp;and&nbsp;<a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1594552">@Kalyani662</a>&nbsp;for hosting the event.&nbsp;</SPAN></P><P><SPAN><A href="https://community.sap.com/t5/sap-codejam/sap-cloud-application-programming-model-with-sap-hana-cloud/ec-p/13788891#M557" target="_blank">https://community.sap.com/t5/sap-codejam/sap-cloud-application-programming-model-with-sap-hana-cloud/ec-p/13788891#M557</A></SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="IMG_1679.jpeg" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/163952iA2C5BF6EDF3FF8E3/image-size/large?v=v2&amp;px=999" role="button" title="IMG_1679.jpeg" alt="IMG_1679.jpeg" /></span><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="IMG_1673.jpeg" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/163951i3D10F69D3ADBBDB4/image-size/large?v=v2&amp;px=999" role="button" title="IMG_1673.jpeg" alt="IMG_1673.jpeg" /></span><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="IMG_1687.jpeg" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/163953i8004D7994015365E/image-size/large?v=v2&amp;px=999" role="button" title="IMG_1687.jpeg" alt="IMG_1687.jpeg" /></span></P><P>&nbsp;</P> 2026-04-02T15:17:06.610000+02:00 https://community.sap.com/t5/sap-codejam/event-driven-integrations-with-sap-integration-suite-advanced-event-mesh/ev-p/14380393 ๐Ÿ‡ฎ๐Ÿ‡ณ Event-driven integrations with SAP Integration Suite, advanced event mesh (Hyderabad, India) 2026-04-22T11:47:12.553000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P><STRONG><FONT color="#FF0000">Registration is closed for this event. The first 25 participants will receive a confirmation email 2 weeks before the event and the rest of the participants will be added to a waiting list and will be considered in case of dropouts.</FONT></STRONG></P><P><SPAN>This <STRONG>SAP CodeJam</STRONG> instance will be about </SPAN><SPAN><STRONG>Event-driven integration</STRONG> using <STRONG>SAP Integration Suite, advanced event mesh.</STRONG> Join us for this event of developer networking and hands-on learning in Hyderabad, India.</SPAN></P><P>We will achieve an integration scenario like the one in the diagram below by the end of the CodeJam.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="final-diagram.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/401380iDB8887AC253CF77A/image-size/large?v=v2&amp;px=999" role="button" title="final-diagram.png" alt="final-diagram.png" /></span></P><P><SPAN>If you are interested in knowing what we will develop during the event, check out the&nbsp;Event-Driven Integrations with SAP Integration Suite, Advanced Event Mesh GitHub repo -&nbsp; </SPAN><A href="https://github.com/SAP-samples/event-driven-integrations-codejam" target="_blank" rel="noopener nofollow noreferrer"><SPAN>https://github.com/SAP-samples/event-driven-integrations-codejam.</SPAN></A><SPAN> The repository contains the details of the integration scenario we will go through during the event.</SPAN></P><P><STRONG>Prerequisites</STRONG></P><UL><LI><SPAN>Bring your own laptop with power adapter and have a supported Web browser of either Google Chrome or Microsoft Edge. Other browsers are not supported.</SPAN></LI><LI><SPAN>It can be helpful to bring a tablet or second laptop, if you have, to have the instructions on a separate screen.</SPAN></LI></UL><P><STRONG>Location and Date&nbsp;</STRONG></P><P><SPAN>This is an</SPAN><SPAN>&nbsp;</SPAN><SPAN>in-person event</SPAN><SPAN>&nbsp;</SPAN><SPAN>only&nbsp;</SPAN><SPAN>(not virtual) and is planned for<STRONG>&nbsp;</STRONG></SPAN><STRONG>Tuesday,&nbsp;June 23, 2026,</STRONG><SPAN>&nbsp;</SPAN><SPAN>from<STRONG>&nbsp;09:30 - 16:00</STRONG>&nbsp;local time in</SPAN><SPAN>&nbsp;</SPAN><SPAN>Hyderabad, India (IST)</SPAN><SPAN>. The language of the event and the content will be&nbsp;</SPAN><SPAN>English</SPAN><SPAN>.</SPAN></P><P><STRONG>Event Address:</STRONG><SPAN><STRONG>&nbsp;&nbsp;</STRONG></SPAN>Bosch Global Software Technologies Private Limited, Sattva Knowledge Park, Tower 1, 14th to 19th Floor Raidurg Village, Serilingampally Mandal Ranga Reddy District, Hyderabad, Telangana, 500081</P><P><STRONG>Google Maps:&nbsp;</STRONG><A href="https://maps.app.goo.gl/jdstx227U4YHzABb6" target="_blank" rel="noopener nofollow noreferrer">https://maps.app.goo.gl/jdstx227U4YHzABb6</A></P><UL><LI><SPAN>The event is kindly being hosted by</SPAN><SPAN><STRONG>&nbsp;Bosch Global Software Technologies Private Limited</STRONG>&nbsp;โ€“&nbsp;<STRONG>THANK YOU!</STRONG></SPAN></LI><LI><SPAN>The instructor is SAP Developer Advocate&nbsp;&nbsp;<A href="https://community.sap.com/t5/user/viewprofilepage/user-id/149819" target="_blank">@ajay_soreng</A></SPAN></LI></UL><P><SPAN>There are essential prerequisites that every attendee must complete before arriving at the event location. You can find the prerequisites here: </SPAN><A href="https://github.com/SAP-samples/event-driven-integrations-codejam/blob/main/prerequisites.md" target="_blank" rel="noopener nofollow noreferrer"><SPAN>https://github.com/SAP-samples/event-driven-integrations-codejam/blob/main/prerequisites.md</SPAN></A><SPAN>.</SPAN></P><P><SPAN>If you wish to attend, <STRONG>please indicate that using the </STRONG></SPAN><STRONG>"Yes"</STRONG><SPAN> button in the </SPAN><SPAN>Will you be attending?</SPAN><SPAN> box on this page.&nbsp;Please note that spaces are limited -&nbsp;</SPAN><SPAN><STRONG>we can only accommodate 25 attendees f</STRONG>or this event</SPAN><SPAN>. </SPAN></P><P><SPAN>In that context, if you indicate that you're attending and later realise you can't, please remove your name so that others get a chance instead. Thanks!</SPAN></P> 2026-04-22T11:47:12.553000+02:00 https://community.sap.com/t5/sap-codejam/build-ai-services-using-sap-cap-and-generative-ai-hub-gurugram-india/ev-p/14382161 ๐Ÿ‡ฎ๐Ÿ‡ณ Build AI services using SAP CAP and Generative AI Hub (Gurugram, India) 2026-04-24T09:39:56.541000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>This</SPAN><SPAN>&nbsp;</SPAN><A href="https://github.com/SAP-samples/codejam-cap-llm/tree/main" target="_blank" rel="noopener nofollow noreferrer"><SPAN>SAP CodeJam</SPAN></A><SPAN>&nbsp;</SPAN><SPAN>event is on the topic of&nbsp;</SPAN><STRONG>Build AI services using the SAP Cloud Application Programming Model, SAP AI Core and Generative AI Hub.</STRONG><SPAN>&nbsp;</SPAN><SPAN>It is beneficial if you have some basic knowledge about JavaScript as we are using the SAP Cloud SDK for AI with the JavaScript variant.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><STRONG>Please note</STRONG> that this is an&nbsp;<STRONG>advanced SAP CAP CodeJam</STRONG> and it requires participants to have <STRONG>basic understanding of the SAP Cloud Application Programming Model</STRONG>.&nbsp;</P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>This CodeJam introduces attendees to aspects of exposing generative artificial intelligence (GenAI) capabilities to execute tasks using a given business context.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>During this event, you will become acquainted with the theory behind GenAI, SAP AI Core, SAP AI Launchpad, and learn how to use the SAP Cloud Application Programming Model to expose AI capabilities through your own OData service.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>While working through the exercises, you will become comfortable creating CDS-based service definitions and integrating them into the SAP Business Technology Platform (BTP) and SAP AI Core Foundation Models.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><FONT face="helvetica">Prerequisites</FONT></STRONG></P><UL class="lia-align-justify" style="text-align : justify;"><LI><FONT face="helvetica"><SPAN>Bring your own laptop with power adapter and have a supported Web browser such as Google Chrome or Firefox installed on your laptop.</SPAN></FONT></LI><LI><FONT face="helvetica"><SPAN>It can be helpful to bring a tablet or second laptop, if you have.</SPAN></FONT></LI><LI><FONT face="helvetica"><SPAN>We will be using the primary environment as described in the prerequisites here </SPAN><A href="https://github.com/SAP-samples/codejam-cap-llm/blob/main/prerequisites.md#prerequisites" target="_blank" rel="noopener nofollow noreferrer"><SPAN>https://github.com/SAP-samples/codejam-cap-llm/blob/main/prerequisites.md#prerequisites</SPAN></A></FONT></LI></UL><P class="lia-align-justify" style="text-align : justify;"><STRONG><FONT face="helvetica">Location and Date</FONT></STRONG></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>This is an</SPAN><SPAN>&nbsp;</SPAN><STRONG>in-person</STRONG><SPAN>&nbsp;</SPAN><SPAN>event only (not virtual) and is planned for</SPAN><SPAN>&nbsp;</SPAN><STRONG>Thursday, May 21st, 2026 </STRONG><SPAN>from 09:30 - 16:00 local time in Gurugram, India. The language of the event and the content will be</SPAN><SPAN>&nbsp;</SPAN><STRONG>English</STRONG><SPAN>.&nbsp;</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN><STRONG>Event Address:</STRONG> Nagarro Software Private Limited, Plot 6B, Sub. Major Laxmi Chand Rd, Maruti Udyog, Sector 18, Sarhol, 122015 Gurugram, Haryana, India</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>Google maps location:&nbsp;<A href="https://maps.app.goo.gl/wwyLK7WG3Jk2Y9qw7" target="_blank" rel="noopener nofollow noreferrer">https://maps.app.goo.gl/wwyLK7WG3Jk2Y9qw7</A></SPAN></FONT></P><UL class="lia-align-justify" style="text-align : justify;"><LI><FONT face="helvetica"><SPAN>The instructors are SAP Developer Advocates&nbsp;</SPAN><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/149819">@ajay_soreng</a>&nbsp;and&nbsp;<a href="https://community.sap.com/t5/user/viewprofilepage/user-id/148298">@shrinivasan_neelamegam</a>&nbsp;</FONT></LI><LI><FONT face="helvetica"><SPAN>Thank you <STRONG>Nagarro Software Private Limited</STRONG>&nbsp;for hosting this event.</SPAN></FONT></LI></UL><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>Please note that spaces are</SPAN><SPAN>&nbsp;</SPAN><STRONG>limited</STRONG><SPAN>! We can</SPAN><SPAN>&nbsp;</SPAN><SPAN>only</SPAN><SPAN>&nbsp;</SPAN><SPAN>accommodate</SPAN><SPAN>&nbsp;</SPAN><STRONG><FONT color="#FF0000">35</FONT></STRONG><SPAN>&nbsp;</SPAN><SPAN>attendees. If you want to attend, register by clicking the</SPAN><SPAN>&nbsp;</SPAN><STRONG>Yes</STRONG><SPAN>&nbsp;</SPAN><SPAN>button in the</SPAN><SPAN>&nbsp;</SPAN><STRONG>Will you be attending</STRONG><SPAN>? section above to the right. About two weeks before the event, you will be asked to confirm your attendance via email.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><EM><FONT face="helvetica">For more information on SAP CodeJam events, head over to the&nbsp;<A href="https://groups.community.sap.com/t5/sap-codejam/gh-p/code-jam" target="_blank" rel="noopener noreferrer">SAP CodeJam group</A>.</FONT></EM></P> 2026-04-24T09:39:56.541000+02:00 https://community.sap.com/t5/sap-codejam/build-ai-services-using-sap-cap-and-generative-ai-hub-gurugram-india/ev-p/14386634 ๐Ÿ‡ฎ๐Ÿ‡ณ Build AI services using SAP CAP and Generative AI Hub (Gurugram, India) 2026-04-30T08:06:41.592000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>This</SPAN><SPAN>&nbsp;</SPAN><A href="https://github.com/SAP-samples/codejam-cap-llm/tree/main" target="_blank" rel="noopener nofollow noreferrer"><SPAN>SAP CodeJam</SPAN></A><SPAN>&nbsp;</SPAN><SPAN>event is on the topic of&nbsp;</SPAN><STRONG>Build AI services using the SAP Cloud Application Programming Model, SAP AI Core and Generative AI Hub.</STRONG><SPAN>&nbsp;</SPAN><SPAN>It is beneficial if you have some basic knowledge about JavaScript as we are using the SAP Cloud SDK for AI with the JavaScript variant.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><STRONG>Please note</STRONG> that this is an&nbsp;<STRONG>advanced SAP CAP CodeJam</STRONG> and it requires participants to have <STRONG>basic understanding of the SAP Cloud Application Programming Model</STRONG>.&nbsp;</P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>This CodeJam introduces attendees to aspects of exposing generative artificial intelligence (GenAI) capabilities to execute tasks using a given business context.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>During this event, you will become acquainted with the theory behind GenAI, SAP AI Core, SAP AI Launchpad, and learn how to use the SAP Cloud Application Programming Model to expose AI capabilities through your own OData service.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>While working through the exercises, you will become comfortable creating CDS-based service definitions and integrating them into the SAP Business Technology Platform (BTP) and SAP AI Core Foundation Models.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><STRONG><FONT face="helvetica">Prerequisites</FONT></STRONG></P><UL class="lia-align-justify" style="text-align : justify;"><LI><FONT face="helvetica"><SPAN>Bring your own laptop with power adapter and have a supported Web browser such as Google Chrome or Firefox installed on your laptop.</SPAN></FONT></LI><LI><FONT face="helvetica"><SPAN>It can be helpful to bring a tablet or second laptop, if you have.</SPAN></FONT></LI><LI><FONT face="helvetica"><SPAN>We will be using the primary environment as described in the prerequisites here </SPAN><A href="https://github.com/SAP-samples/codejam-cap-llm/blob/main/prerequisites.md#prerequisites" target="_blank" rel="noopener nofollow noreferrer"><SPAN>https://github.com/SAP-samples/codejam-cap-llm/blob/main/prerequisites.md#prerequisites</SPAN></A></FONT></LI></UL><P class="lia-align-justify" style="text-align : justify;"><STRONG><FONT face="helvetica">Location and Date</FONT></STRONG></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>This is an</SPAN><SPAN>&nbsp;</SPAN><STRONG>in-person</STRONG><SPAN>&nbsp;</SPAN><SPAN>event only (not virtual) and is planned for</SPAN><SPAN>&nbsp;<STRONG>Friday</STRONG></SPAN><STRONG>, May 22nd, 2026 </STRONG><SPAN>from 09:30 - 16:00 local time in Gurugram, India. The language of the event and the content will be</SPAN><SPAN>&nbsp;</SPAN><STRONG>English</STRONG><SPAN>.&nbsp;</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN><STRONG>Event Address:&nbsp;</STRONG></SPAN></FONT>WeWork Two Horizon Centre, 5th Floor, Two Horizon Centre, 5th Floor, DLF, DLF Phase 5, Gurugram, Haryana 122009.</P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>Google maps location: <A href="https://eur03.safelinks.protection.outlook.com/?url=https%3A%2F%2Fmaps.app.goo.gl%2FB3LYKZoQxKMGhLby8&amp;data=05%7C02%7Cajay.soreng%40sap.com%7C1a80e6b2481c4da4f93108dea99f01cd%7C42f7676cf455423c82f6dc2d99791af7%7C0%7C0%7C639134697570390829%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&amp;sdata=qkrB4Y9jqwMEDYDXQ7aUk8Ik2ezuzCnbvoA84F9qORk%3D&amp;reserved=0" target="_blank" rel="noopener nofollow noreferrer">https://maps.app.goo.gl/B3LYKZoQxKMGhLby8</A></SPAN></FONT></P><UL class="lia-align-justify" style="text-align : justify;"><LI><FONT face="helvetica"><SPAN>The instructors are SAP Developer Advocates&nbsp;</SPAN><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/149819">@ajay_soreng</a>&nbsp;and&nbsp;<a href="https://community.sap.com/t5/user/viewprofilepage/user-id/148298">@shrinivasan_neelamegam</a>&nbsp;</FONT></LI><LI><FONT face="helvetica"><SPAN>Thank you <STRONG>MINDSET Consulting </STRONG>for hosting this event.</SPAN></FONT></LI></UL><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica"><SPAN>Please note that spaces are</SPAN><SPAN>&nbsp;</SPAN><STRONG>limited</STRONG><SPAN>! We can</SPAN><SPAN>&nbsp;</SPAN><SPAN>only</SPAN><SPAN>&nbsp;</SPAN><SPAN>accommodate</SPAN><SPAN>&nbsp;25</SPAN><SPAN>&nbsp;</SPAN><SPAN>attendees. If you want to attend, register by clicking the</SPAN><SPAN>&nbsp;</SPAN><STRONG>Yes</STRONG><SPAN>&nbsp;</SPAN><SPAN>button in the</SPAN><SPAN>&nbsp;</SPAN><STRONG>Will you be attending</STRONG><SPAN>? section above to the right. About two weeks before the event, you will be asked to confirm your attendance via email.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><EM><FONT face="helvetica">For more information on SAP CodeJam events, head over to the&nbsp;<A href="https://groups.community.sap.com/t5/sap-codejam/gh-p/code-jam" target="_blank" rel="noopener noreferrer">SAP CodeJam group</A>.</FONT></EM></P> 2026-04-30T08:06:41.592000+02:00 https://community.sap.com/t5/integration-forum/may-2026-developer-challenge-week-1-getting-familiar-with-the-events/m-p/14395118#M278 Re: May 2026 Developer Challenge โ€” Week 1: Getting familiar with the events 2026-05-13T12:45:45.119000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P>My submission for week1 . Sent the event via&nbsp;<SPAN>Business Event Generator&nbsp;app and received via Solace AEM broker.&nbsp;</SPAN></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Screenshot 2026-05-13 at 4.03.54โ€ฏPM.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/409246iCD18138D15B0FB10/image-size/medium?v=v2&amp;px=400" role="button" title="Screenshot 2026-05-13 at 4.03.54โ€ฏPM.png" alt="Screenshot 2026-05-13 at 4.03.54โ€ฏPM.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Screenshot 2026-05-13 at 4.01.45โ€ฏPM.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/409247iAEBD758393465091/image-size/medium?v=v2&amp;px=400" role="button" title="Screenshot 2026-05-13 at 4.01.45โ€ฏPM.png" alt="Screenshot 2026-05-13 at 4.01.45โ€ฏPM.png" /></span></P><P>โ€ƒAnother one via try me feature. I choose solace because SAP Integration Suite AEM is solace under the hood.&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Screenshot 2026-05-13 at 4.11.18โ€ฏPM.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/409263i4FE9A5FE5AF3CEB0/image-size/medium?v=v2&amp;px=400" role="button" title="Screenshot 2026-05-13 at 4.11.18โ€ฏPM.png" alt="Screenshot 2026-05-13 at 4.11.18โ€ฏPM.png" /></span></P><P>โ€ƒ</P><P>&nbsp;</P> 2026-05-13T12:45:45.119000+02:00 https://community.sap.com/t5/integration-forum/may-2026-developer-challenge-week-2-connecting-to-the-broker-and-consuming/m-p/14397039#M284 Re: May 2026 Developer Challenge โ€” Week 2: Connecting to the broker and consuming events 2026-05-15T13:33:50.301000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P>I took a generic non sap flow to start with the Business partner generator application ==&gt; solace AEM ==&gt; solace message queue ==&gt; RDP (event consumed here and sent to webhook )==&gt;&nbsp;<A href="https://webhook.site/" target="_blank" rel="nofollow noopener noreferrer">https://webhook.site/</A>&nbsp;(event finally reached&nbsp; here)</P><P>I tried the last part with n8n trial but currently faced some challenges. I still try that again.&nbsp;</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="event payload in consumer webhook.site" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/410423i80E8B41EF6D645E0/image-size/large?v=v2&amp;px=999" role="button" title="Screenshot 2026-05-15 at 4.53.27โ€ฏPM.png" alt="event payload in consumer webhook.site" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">event payload in consumer webhook.site</span></span><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Cloud event via Business Partner app" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/410422iE76D690DC6C3A00A/image-size/large?v=v2&amp;px=999" role="button" title="Screenshot 2026-05-15 at 4.53.17โ€ฏPM.png" alt="Cloud event via Business Partner app" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Cloud event via Business Partner app</span></span><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Rest delivery point" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/410424i960B2586FF96B4FA/image-size/large?v=v2&amp;px=999" role="button" title="Screenshot 2026-05-15 at 4.44.23โ€ฏPM.png" alt="Rest delivery point" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Rest delivery point</span></span></P> 2026-05-15T13:33:50.301000+02:00 https://community.sap.com/t5/sap-codejam/event-driven-integrations-with-sap-integration-suite-advanced-event-mesh/ec-p/14418874#M1189 Re: ๐Ÿ‡ฎ๐Ÿ‡ณ Event-driven integrations with SAP Integration Suite, advanced event mesh (Hyderabad, India 2026-06-15T10:45:02.969000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P>Hi&nbsp;<a href="https://community.sap.com/t5/user/viewprofilepage/user-id/2306675">@pavankalyansiriki</a>&nbsp;thanks for reaching out. As of now the seats are full. We will get back if there is any withdrawal/cancellation.</P> 2026-06-15T10:45:02.969000+02:00 https://community.sap.com/t5/sap-codejam/event-driven-integrations-with-sap-integration-suite-advanced-event-mesh/ec-p/14424405#M1251 Re: ๐Ÿ‡ฎ๐Ÿ‡ณ Event-driven integrations with SAP Integration Suite, advanced event mesh (Hyderabad, India 2026-06-22T12:23:41.825000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P>Hi&nbsp;<a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1512863">@adf</a>&nbsp;CodeJam event has limited seats. The email to the first 25 participants was already sent by the host.&nbsp;</P> 2026-06-22T12:23:41.825000+02:00 https://community.sap.com/t5/sap-codejam/build-ai-services-using-sap-cap-and-generative-ai-hub-singapore/ev-p/14431158 ๐Ÿ‡ธ๐Ÿ‡ฌ Build AI services using SAP CAP and Generative AI Hub (Singapore) 2026-07-01T09:54:28.349000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica" size="4"><SPAN>This</SPAN><SPAN>&nbsp;</SPAN><A href="https://github.com/SAP-samples/codejam-cap-llm/tree/main" target="_blank" rel="noopener nofollow noreferrer"><SPAN>SAP CodeJam</SPAN></A><SPAN>&nbsp;</SPAN><SPAN>event is on the topic of&nbsp;</SPAN><STRONG>Build AI services using the SAP Cloud Application Programming Model, SAP AI Core and Generative AI Hub.</STRONG><SPAN>&nbsp;</SPAN><SPAN>It is beneficial if you have some basic knowledge about JavaScript as we are using the SAP Cloud SDK for AI with the JavaScript variant.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT size="4"><STRONG>Please note</STRONG> that this is an&nbsp;<STRONG>advanced SAP CAP CodeJam</STRONG> and it requires participants to have <STRONG>basic understanding of the SAP Cloud Application Programming Model</STRONG>.&nbsp;</FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica" size="4"><SPAN>This CodeJam introduces attendees to aspects of exposing generative artificial intelligence (GenAI) capabilities to execute tasks using a given business context.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica" size="4"><SPAN>During this event, you will become acquainted with the theory behind GenAI, SAP AI Core, SAP AI Launchpad, and learn how to use the SAP Cloud Application Programming Model to expose AI capabilities through your own OData service.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica" size="4"><SPAN>While working through the exercises, you will become comfortable creating CDS-based service definitions and integrating them into the SAP Business Technology Platform (BTP) and SAP AI Core Foundation Models.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT size="4"><STRONG><FONT face="helvetica">Prerequisites</FONT></STRONG></FONT></P><UL class="lia-align-justify" style="text-align : justify;"><LI><FONT face="helvetica" size="4"><SPAN>Bring your own laptop with power adapter and have a supported Web browser such as Google Chrome or Firefox installed on your laptop.</SPAN></FONT></LI><LI><FONT face="helvetica" size="4"><SPAN>It can be helpful to bring a tablet or second laptop, if you have.</SPAN></FONT></LI><LI><FONT face="helvetica" size="4"><SPAN>We will be using the primary environment as described in the prerequisites here </SPAN><A href="https://github.com/SAP-samples/codejam-cap-llm/blob/main/prerequisites.md#prerequisites" target="_blank" rel="noopener nofollow noreferrer"><SPAN>https://github.com/SAP-samples/codejam-cap-llm/blob/main/prerequisites.md#prerequisites</SPAN></A></FONT></LI></UL><P class="lia-align-justify" style="text-align : justify;"><FONT size="4"><STRONG><FONT face="helvetica">Location and Date</FONT></STRONG></FONT></P><P><FONT face="arial,helvetica,sans-serif" size="4"><SPAN>This is a free&nbsp;<STRONG>in-person</STRONG>&nbsp;event and is planned for&nbsp;</SPAN><SPAN>Wednesday,&nbsp;<STRONG>August 26</STRONG>, in Singapore&nbsp;</SPAN><SPAN>from&nbsp;<STRONG>10:00 AM to 4:00 PM</STRONG>&nbsp;local time. The event's language and content will be in&nbsp;<STRONG>English</STRONG>.&nbsp;</SPAN></FONT></P><P><FONT face="arial,helvetica,sans-serif" size="4"><STRONG>Event Address:</STRONG><SPAN>&nbsp;SAP Asia Pte Ltd.30 Pasir Panjang Rd, Singapore 117440, Located in:&nbsp;</SPAN><A href="https://www.google.com/search?sca_esv=16c2f173e47a52dc&amp;rlz=1C5GCEM_enIN1174IN1174&amp;sxsrf=APpeQnuVnSbIZ6dwLBgui3DR_lpSVZfmIA:1782814299372&amp;q=Mapletree+Business+City&amp;ludocid=16207153647496196455&amp;lsig=AB86z5Ub3AulVD7ASzTJEwWSqaL6&amp;sa=X&amp;ved=2ahUKEwjIwI7o3K6VAxWk4DgGHdt4K0wQ8G0oAHoECEAQAQ" target="_blank" rel="noopener nofollow noreferrer"><SPAN>Mapletree Business City</SPAN></A><SPAN>.</SPAN></FONT></P><P><FONT face="arial,helvetica,sans-serif" size="4"><STRONG>Google Maps:</STRONG><SPAN>&nbsp;</SPAN><A href="https://maps.app.goo.gl/6ZEMBPXsthyshYH2A" target="_blank" rel="noopener nofollow noreferrer"><SPAN>https://maps.app.goo.gl/6ZEMBPXsthyshYH2A</SPAN></A></FONT></P><UL><LI><FONT face="arial,helvetica,sans-serif" size="4"><SPAN>A huge thank you to&nbsp;</SPAN><STRONG>Mark9 Solutions</STRONG><SPAN>&nbsp;for hosting this event.</SPAN></FONT></LI><LI><FONT face="arial,helvetica,sans-serif" size="4"><SPAN>The instructors are SAP Developer Advocates&nbsp;<A href="https://community.sap.com/t5/user/viewprofilepage/user-id/149819" target="_blank">@ajay_soreng</A>&nbsp; and</SPAN><SPAN>&nbsp;</SPAN><A href="https://community.sap.com/t5/user/viewprofilepage/user-id/148298" target="_blank"><SPAN>@shrinivasan_neelamegam</SPAN></A><SPAN>&nbsp;</SPAN></FONT></LI><LI><FONT size="4"><SPAN>In collaboration with <STRONG>Sanjog Tiwari</STRONG>, Customer Success Lead โ€” AI, Data &amp; Platform, SEA Team</SPAN></FONT></LI></UL><P class="lia-align-justify" style="text-align : justify;"><FONT face="arial,helvetica,sans-serif" size="4" color="#000000"><SPAN>Please note that spaces are limited!</SPAN><SPAN>&nbsp;We can only accommodate&nbsp;</SPAN><STRONG><FONT color="#FF0000">30</FONT></STRONG><SPAN>&nbsp;attendees.&nbsp;If you want to attend, register by clicking the&nbsp;</SPAN><STRONG>Yes</STRONG><SPAN>&nbsp;button in the&nbsp;</SPAN><STRONG>Will you be attending?</STRONG><SPAN>&nbsp;section above to the right.&nbsp;</SPAN></FONT><FONT size="4" color="#000000">Approximately<SPAN>&nbsp;</SPAN><STRONG>2 weeks</STRONG><SPAN>&nbsp;</SPAN>prior to the event, you will receive an email to confirm your attendance, along with further instructions and details.</FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT size="4"><EM><FONT face="helvetica">For more information on SAP CodeJam events, head over to the&nbsp;<A href="https://groups.community.sap.com/t5/sap-codejam/gh-p/code-jam" target="_blank" rel="noopener noreferrer">SAP CodeJam group</A>.</FONT></EM></FONT></P><P>&nbsp;</P><P>&nbsp;</P> 2026-07-01T09:54:28.349000+02:00 https://community.sap.com/t5/sap-codejam/build-ai-services-using-sap-cap-and-generative-ai-hub-bengaluru-india/ev-p/14431171 ๐Ÿ‡ฎ๐Ÿ‡ณ Build AI services using SAP CAP and Generative AI Hub (Bengaluru, India) 2026-07-01T10:09:38.778000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P class="lia-align-justify" style="text-align : justify;"><STRONG><FONT color="#FF0000">REGISTRATION IS NOW CLOSED FOR THIS EVENT&nbsp;-&nbsp;We will send confirmation emails to first 35 attendees a couple of weeks before the event and we will manage a waitlist, in case some of the RSVPs are unable to make it to the event.</FONT></STRONG></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica" size="4"><SPAN>This</SPAN><SPAN>&nbsp;</SPAN><A href="https://github.com/SAP-samples/codejam-cap-llm/tree/main" target="_blank" rel="noopener nofollow noreferrer"><SPAN>SAP CodeJam</SPAN></A><SPAN>&nbsp;</SPAN><SPAN>event is on the topic of&nbsp;</SPAN><STRONG>Build AI services using the SAP Cloud Application Programming Model, SAP AI Core and Generative AI Hub.</STRONG><SPAN>&nbsp;</SPAN><SPAN>It is beneficial if you have some basic knowledge about JavaScript as we are using the SAP Cloud SDK for AI with the JavaScript variant.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT size="4"><STRONG>Please note</STRONG> that this is an&nbsp;<STRONG>advanced SAP CAP CodeJam</STRONG> and it requires participants to have <STRONG>basic understanding of the SAP Cloud Application Programming Model</STRONG>.&nbsp;</FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica" size="4"><SPAN>This CodeJam introduces attendees to aspects of exposing generative artificial intelligence (GenAI) capabilities to execute tasks using a given business context.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica" size="4"><SPAN>During this event, you will become acquainted with the theory behind GenAI, SAP AI Core, SAP AI Launchpad, and learn how to use the SAP Cloud Application Programming Model to expose AI capabilities through your own OData service.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT face="helvetica" size="4"><SPAN>While working through the exercises, you will become comfortable creating CDS-based service definitions and integrating them into the SAP Business Technology Platform (BTP) and SAP AI Core Foundation Models.</SPAN></FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT size="4"><STRONG><FONT face="helvetica">Prerequisites</FONT></STRONG></FONT></P><UL class="lia-align-justify" style="text-align : justify;"><LI><FONT face="helvetica" size="4"><SPAN>Bring your own laptop with power adapter and have a supported Web browser such as Google Chrome or Firefox installed on your laptop.</SPAN></FONT></LI><LI><FONT face="helvetica" size="4"><SPAN>It can be helpful to bring a tablet or second laptop, if you have.</SPAN></FONT></LI><LI><FONT face="helvetica" size="4"><SPAN>We will be using the primary environment as described in the prerequisites here </SPAN><A href="https://github.com/SAP-samples/codejam-cap-llm/blob/main/prerequisites.md#prerequisites" target="_blank" rel="noopener nofollow noreferrer"><SPAN>https://github.com/SAP-samples/codejam-cap-llm/blob/main/prerequisites.md#prerequisites</SPAN></A></FONT></LI></UL><P class="lia-align-justify" style="text-align : justify;"><STRONG><FONT face="arial,helvetica,sans-serif" size="4">Location and Date:</FONT></STRONG></P><P><FONT face="arial,helvetica,sans-serif" size="4"><SPAN>This is a free&nbsp;<STRONG>in-person</STRONG>&nbsp;event and is planned for Tuesday</SPAN><SPAN>,&nbsp;<STRONG>August 18</STRONG>, in Bengaluru&nbsp;</SPAN><SPAN>from&nbsp;<STRONG>9:30 AM to 4:00 PM</STRONG>&nbsp;local time. The event's language and content will be in&nbsp;<STRONG>English</STRONG>.&nbsp;</SPAN></FONT></P><P><FONT face="arial,helvetica,sans-serif" size="4"><STRONG>Event Address:</STRONG></FONT></P><DIV class=""><FONT face="arial,helvetica,sans-serif">B</FONT>osch Global Software Technologies</DIV><DIV class="">360ยฐ Business Park, Electronic City Phase I, Electronic City, Bengaluru, Doddathoguru, Karnataka 560100</DIV><P><FONT face="arial,helvetica,sans-serif" size="4"><STRONG>Google Maps:<SPAN>&nbsp;</SPAN><A href="https://maps.app.goo.gl/18xB6jTQbUoyEMhp8" target="_blank" rel="noopener nofollow noreferrer">https://maps.app.goo.gl/18xB6jTQbUoyEMhp8</A></STRONG></FONT></P><UL><LI><FONT face="arial,helvetica,sans-serif" size="4"><SPAN>&nbsp;Special thanks to&nbsp;<STRONG><FONT face="arial,helvetica,sans-serif">Bosch Global Software Technologies</FONT>&nbsp;</STRONG></SPAN><SPAN>&nbsp;for hosting this event.</SPAN></FONT></LI></UL><UL><LI><FONT face="arial,helvetica,sans-serif" size="4"><SPAN>The instructors are SAP Developer Advocates&nbsp;<A href="https://community.sap.com/t5/user/viewprofilepage/user-id/149819" target="_blank">@ajay_soreng</A>&nbsp; and</SPAN><SPAN>&nbsp;</SPAN><A href="https://community.sap.com/t5/user/viewprofilepage/user-id/148298" target="_blank"><SPAN>@shrinivasan_neelamegam</SPAN></A><SPAN>&nbsp;</SPAN></FONT></LI></UL><P class="lia-align-justify" style="text-align : justify;"><FONT face="arial,helvetica,sans-serif" size="4" color="#000000"><SPAN>Please note that spaces are limited!</SPAN><SPAN>&nbsp;We can only accommodate&nbsp;</SPAN><STRONG><FONT color="#FF0000">35</FONT></STRONG><SPAN>&nbsp;attendees.&nbsp;If you want to attend, register by clicking the&nbsp;</SPAN><STRONG>Yes</STRONG><SPAN>&nbsp;button in the&nbsp;</SPAN><STRONG>Will you be attending?</STRONG><SPAN>&nbsp;section above to the right.&nbsp;</SPAN></FONT><FONT size="4" color="#000000">Approximately<SPAN>&nbsp;</SPAN><STRONG>2 weeks</STRONG><SPAN>&nbsp;</SPAN>prior to the event, you will receive an email to confirm your attendance, along with further instructions and details.</FONT></P><P class="lia-align-justify" style="text-align : justify;"><FONT size="4"><EM><FONT face="helvetica">For more information on SAP CodeJam events, head over to the&nbsp;<A href="https://groups.community.sap.com/t5/sap-codejam/gh-p/code-jam" target="_blank" rel="noopener noreferrer">SAP CodeJam group</A>.</FONT></EM></FONT></P> 2026-07-01T10:09:38.778000+02:00 https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-snake-amp-ladder-with-sap-cap-status-transition/ba-p/14429486 ๐Ÿ July Developer Challenge โ€” Snake & Ladder with SAP CAP Status Transition Flows 2026-07-01T15:14:27.093000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <H1 id="sap-developer-challenge---cap-knowledge---intro" id="toc-hId-1689279291">SAP Developer Challenge - Snake &amp; Ladder with SAP CAP Status Transition Flows</H1><P><SPAN>Welcome to the&nbsp;</SPAN><STRONG>July 2026 SAP Developer Challenge</STRONG><SPAN>&nbsp;โ€” a 4-week hands-on series where you build a&nbsp;</SPAN><STRONG>multiplayer Snake &amp; Ladder game</STRONG><SPAN>&nbsp;as a fully functional&nbsp;</SPAN><STRONG>SAP CAP service</STRONG><SPAN>, powered by&nbsp;</SPAN><STRONG>CAP Status Flows</STRONG><SPAN>&nbsp;(</SPAN><CODE>@flow.status</CODE><SPAN>).</SPAN></P><HR /><H2 id="the-scenario" id="toc-hId-1621848505">The Scenario</H2><P class="">We will be building the backend engine for an online Snake &amp; Ladder game. Each player's position on the board is a<SPAN>&nbsp;</SPAN><STRONG>state</STRONG><SPAN>&nbsp;</SPAN>โ€” and every move, snake bite, and ladder climb is a<SPAN>&nbsp;</SPAN><STRONG>status transition</STRONG><SPAN>&nbsp;</SPAN>modelled using CAP's<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/39488">@flow</a>.status</CODE><SPAN>&nbsp;</SPAN>annotations.</P><P class="">By the end of July, you'll have:</P><UL class=""><LI>A running<SPAN>&nbsp;</SPAN><STRONG>CAP Node.js service</STRONG><SPAN>&nbsp;</SPAN>with CDS entities and status-flow bound actions</LI><LI>A<SPAN>&nbsp;</SPAN><STRONG>multiplayer game engine</STRONG><SPAN>&nbsp;</SPAN>with turn management, dice rolls, snake/ladder triggers, and CAP events</LI><LI>A<SPAN>&nbsp;</SPAN><STRONG>SAP Fiori UI</STRONG><SPAN>&nbsp;</SPAN>displaying the live board โ€” participants just plug in their service URL</LI><LI>Integration with the<SPAN>&nbsp;</SPAN><STRONG>CAP MCP server</STRONG><SPAN>&nbsp;</SPAN>to let an AI assistant drive moves via natural language</LI></UL><HR /><H2 id="what-makes-this-interesting" id="toc-hId-1425335000">What Makes This Interesting</H2><P class="">CAP's<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/39488">@flow</a>.status</CODE><SPAN>&nbsp;</SPAN>feature gives you out-of-the-box:</P><UL class=""><LI><STRONG>Pre-validation</STRONG><SPAN>&nbsp;</SPAN>โ€” a<SPAN>&nbsp;</SPAN><CODE>@from: [#Playing]</CODE><SPAN>&nbsp;</SPAN>annotation rejects any action called in the wrong state (HTTP 409), no custom code needed</LI><LI><STRONG>Automatic state transitions</STRONG><SPAN>&nbsp;</SPAN>โ€”<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1392811">@TO</a>: #Finished</CODE><SPAN>&nbsp;</SPAN>updates the entity after your action returns</LI><LI><STRONG>History tracking</STRONG><SPAN>&nbsp;</SPAN>โ€”<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1392811">@TO</a>: $flow.previous</CODE><SPAN>&nbsp;</SPAN>restores the previous state (perfect for a snake bite that temporarily blocks a player)</LI><LI><STRONG>Fiori integration</STRONG><SPAN>&nbsp;</SPAN>โ€” buttons in the UI are automatically enabled/disabled based on the current status</LI></UL><P class="">Snake &amp; Ladder maps directly onto this model: a player's turn is a state machine, and every square is a potential transition trigger.</P><H2 id="challenge-format" id="toc-hId-1228821495">Challenge Format</H2><P class="">Each week a blog post is published on SAP Community with the task, hints, and code guidance.<SPAN>&nbsp;</SPAN><STRONG>Submit your answer as a reply comment on that week's blog post.</STRONG></P><P>Week Dates Title Core Topic Blog Post</P><TABLE><TBODY><TR><TD>1</TD><TD>6โ€“12 Jul</TD><TD><A href="https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-week-1-model-the-board/ba-p/14431463" target="_self">Model the Board</A></TD><TD>CDS entities, status enum,<SPAN>&nbsp;</SPAN><CODE>@flow.status</CODE></TD><TD><A href="https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-week-1-model-the-board/ba-p/14431463" target="_self">link</A></TD></TR><TR><TD>2</TD><TD>13โ€“19 Jul</TD><TD><A href="https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-week-2-roll-amp-move/ba-p/14438102" target="_self">Roll &amp; Move</A></TD><TD>Bound actions,<SPAN>&nbsp;</SPAN><CODE>@from</CODE><SPAN>&nbsp;</SPAN>/<SPAN>&nbsp;</SPAN><CODE>@to</CODE><SPAN>&nbsp;</SPAN>transitions</TD><TD><A href="https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-week-2-roll-amp-move/ba-p/14438102" target="_self">link</A></TD></TR><TR><TD>3</TD><TD>20โ€“26 Jul</TD><TD><A href="https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-week-3-multiplayer-amp-events/ba-p/14444431" target="_self">Multiplayer &amp; Events</A></TD><TD>Turn management, CAP events, snake/ladder logic</TD><TD><A href="https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-week-3-multiplayer-amp-events/ba-p/14444431" target="_self">link</A>&nbsp;</TD></TR><TR><TD>4</TD><TD>27โ€“31 Jul</TD><TD>Fiori UI &amp; CAP MCP</TD><TD>Board UI, MCP server</TD><TD>TBD</TD></TR></TBODY></TABLE><HR /><H2 id="prerequisites" id="toc-hId-1032307990">Prerequisites</H2><UL class=""><LI>Node.js 20+</LI><LI><CODE>@sap/cds-dk</CODE><SPAN>&nbsp;</SPAN>installed globally:<SPAN>&nbsp;</SPAN><CODE>npm install -g @sap/cds-dk</CODE></LI><LI>VS Code with the SAP CDS Language Support extension</LI><LI>Basic familiarity with CAP (entities, services,<SPAN>&nbsp;</SPAN><CODE>cds watch</CODE>)</LI></UL><P class="">No prior knowledge of CAP status flows is required โ€” Week 1 introduces everything from scratch.</P><HR /><H2 id="the-board" id="toc-hId-835794485">The Board</H2><P class="">The game uses a classic 10ร—10 board (squares 1โ€“100). Key squares:</P><H3 id="ladders-climb-up" id="toc-hId-768363699">Ladders (climb up)</H3><P>Bottom Top Effect</P><TABLE><TBODY><TR><TD>2</TD><TD>38</TD><TD>Big early boost</TD></TR><TR><TD>8</TD><TD>31</TD><TD>Good start</TD></TR><TR><TD>15</TD><TD>26</TD><TD>Small step up</TD></TR><TR><TD>21</TD><TD>42</TD><TD>Mid-board jump</TD></TR><TR><TD>28</TD><TD>84</TD><TD>The long ladder โ€” almost wins it</TD></TR><TR><TD>36</TD><TD>44</TD><TD>Short gain</TD></TR><TR><TD>51</TD><TD>67</TD><TD>Solid advance</TD></TR><TR><TD>71</TD><TD>91</TD><TD>Late-game boost</TD></TR><TR><TD>78</TD><TD>98</TD><TD>So close to victory</TD></TR></TBODY></TABLE><H3 id="single-headed-snakes-slide-back" id="toc-hId-571850194">Single-Headed Snakes (slide back)</H3><P>Head Tail Severity</P><TABLE><TBODY><TR><TD>99</TD><TD>7</TD><TD>Devastating โ€” one square from the finish</TD></TR><TR><TD>76</TD><TD>20</TD><TD>Major setback</TD></TR><TR><TD>43</TD><TD>18</TD><TD>Mid-game punisher</TD></TR></TBODY></TABLE><H3 id="double-headed-snakes-two-biting-ends-one-shared-tail" id="toc-hId-375336689">Double-Headed Snakes (two biting ends, one shared tail)</H3><P class="">These special snakes have<SPAN>&nbsp;</SPAN><STRONG>two heads on different squares</STRONG><SPAN>&nbsp;</SPAN>that share a single tail. Landing on either head square sends you to the same tail square.</P><P>Head 1 Head 2 Shared Tail</P><TABLE><TBODY><TR><TD>89</TD><TD>72</TD><TD>50</TD></TR><TR><TD>54</TD><TD>63</TD><TD>34</TD></TR><TR><TD>40</TD><TD>47</TD><TD>3</TD></TR></TBODY></TABLE><P class="">The double-headed snake maps to CAP's<SPAN>&nbsp;</SPAN><CODE>@from: [#Head1, #Head2]</CODE><SPAN>&nbsp;</SPAN>โ€” the same transition triggered by multiple entry states.</P><HR /><H2 id="reusable-board-ui" id="toc-hId-49740465">Reusable Board UI</H2><P class="">A ready-made HTML/JS board will be provided later. Participants plug in their CAP service URL and see the live board โ€” no frontend coding required for the challenge.</P><HR /><H2 id="resources" id="toc-hId-200481317">Resources</H2><UL class=""><UL class=""><LI><A href="https://cap.cloud.sap/docs/guides/services/status-flows" target="_blank" rel="noopener nofollow noreferrer">CAP Status Transition Flows Documentation</A></LI><LI><A href="https://cap.cloud.sap/docs/node.js/" target="_blank" rel="noopener nofollow noreferrer">CAP Node.js Documentation</A></LI><LI><A href="https://cap.cloud.sap/docs/get-started/#initial-setup" target="_self" rel="nofollow noopener noreferrer">Initial Setup</A>&nbsp;</LI></UL></UL><P>&nbsp;</P> 2026-07-01T15:14:27.093000+02:00 https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-week-1-model-the-board/ba-p/14431463 ๐Ÿ July Developer Challenge Week 1 โ€” Model the Board 2026-07-01T15:14:58.896000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <H1 id="week-1--model-the-board" id="toc-hId-1689964419">Week 1 โ€” Model the Board</H1><BLOCKQUOTE dir="auto"><P class="" data-unlink="true"><STRONG>Challenge series:</STRONG><SPAN>&nbsp;</SPAN><A href="https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-snake-amp-ladder-with-sap-cap-status-transition/ba-p/14429486" target="_self">Snake &amp; Ladder Developer Challenge</A>&nbsp;<SPAN>&nbsp;</SPAN>| Week 1 of 4<SPAN>&nbsp;</SPAN><STRONG>Dates:</STRONG><SPAN>&nbsp;6</SPAN>โ€“12 July 2026</P></BLOCKQUOTE><HR /><H2 id="the-setup" id="toc-hId-1622533633">The Setup</H2><P class="">Every game needs a board. Before any snake can bite or ladder can lift, you need a data model that captures the state of a game โ€” players, positions, and crucially,<SPAN>&nbsp;</SPAN><STRONG>whose turn it is</STRONG><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><STRONG>what can happen next</STRONG>.</P><P class="">This week we will build the CDS foundation: entities, a status enum, and your first<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/39488">@flow</a>.status</CODE><SPAN>&nbsp;</SPAN>annotation. No game logic yet โ€” just clean modelling and a running CAP service.</P><HR /><H2 id="what-youll-build-this-week" id="toc-hId-1426020128">What You'll Build This Week</H2><UL class=""><LI>A new CAP Node.js project:<SPAN>&nbsp;</SPAN><CODE>snake-ladder</CODE></LI><LI>CDS entities for<SPAN>&nbsp;</SPAN><CODE>GameSession</CODE>,<SPAN>&nbsp;</SPAN><CODE>Player</CODE>, and<SPAN>&nbsp;</SPAN><CODE>BoardSquare</CODE></LI><LI>A<SPAN>&nbsp;</SPAN><CODE>TurnStatus</CODE><SPAN>&nbsp;</SPAN>enum with the game states</LI><LI>Your first<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/39488">@flow</a>.status</CODE><SPAN>&nbsp;</SPAN>annotation</LI><LI>Seed data: a 10ร—10 board with all snakes and ladders pre-loaded</LI><LI>A running service at<SPAN>&nbsp;</SPAN><CODE><A href="http://localhost:4004" target="_blank" rel="noopener nofollow noreferrer">http://localhost:4004</A></CODE></LI></UL><HR /><H2 id="prerequisites" id="toc-hId-1229506623">Prerequisites</H2><UL class=""><LI>Node.js 20+</LI><LI><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/2302137">@SAP</a>/cds-dk</CODE><SPAN>&nbsp;</SPAN>installed globally:</LI></UL><PRE><CODE>npm install -g @sap/cds-dk</CODE></PRE><UL class=""><LI>VS Code with the<SPAN>&nbsp;</SPAN><A href="https://marketplace.visualstudio.com/items?itemName=SAPSE.vscode-cds" target="_blank" rel="noopener nofollow noreferrer">SAP CDS Language Support</A><SPAN>&nbsp;</SPAN>extension. Check&nbsp;<A href="https://cap.cloud.sap/docs/get-started/#initial-setup" target="_blank" rel="nofollow noopener noreferrer">Initial Setup</A><SPAN>&nbsp;for more details how to do a local setup.&nbsp;</SPAN></LI></UL><HR /><H2 id="background-cap-status-flows-in-60-seconds" id="toc-hId-1032993118">Background: CAP Status Flows in 60 Seconds</H2><P class="">CAP's<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/39488">@flow</a>.status</CODE><SPAN>&nbsp;</SPAN>feature turns a regular enum field into a controlled state machine. You annotate:</P><UL class=""><LI>The<SPAN>&nbsp;</SPAN><STRONG>entity</STRONG><SPAN>&nbsp;</SPAN>with<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/39488">@flow</a>.status: FieldName</CODE><SPAN>&nbsp;</SPAN>to designate which field drives the state machine</LI><LI><STRONG>Bound actions</STRONG><SPAN>&nbsp;</SPAN>with<SPAN>&nbsp;</SPAN><CODE>@from: [#StateA]</CODE><SPAN>&nbsp;</SPAN>to declare which states allow that action</LI><LI><STRONG>Bound actions</STRONG><SPAN>&nbsp;</SPAN>with<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1392811">@TO</a>: #StateB</CODE><SPAN>&nbsp;</SPAN>to declare the target state after the action runs</LI></UL><P class="">CAP handles the rest automatically:</P><UL class=""><LI>Validates the current state before the action runs (returns<SPAN>&nbsp;</SPAN><STRONG>HTTP 409</STRONG><SPAN>&nbsp;</SPAN>if wrong state)</LI><LI>Updates the status field after the action completes</LI><LI>Generates Fiori annotations to enable/disable buttons based on current state</LI></UL><P class="">No custom validation code. No manual status updates. The framework enforces the rules.</P><HR /><H2 id="step-by-step" id="toc-hId-836479613">Step-by-Step</H2><H3 id="1-initialise-the-cap-project" id="toc-hId-769048827">1. Initialise the CAP project</H3><PRE><CODE>cds init snake-ladder --nodejs <SPAN class="">cd</SPAN> snake-ladder npm install code .</CODE></PRE><H3 id="2-create-the-data-model" id="toc-hId-572535322">2. Create the data model</H3><P class="">Create<SPAN>&nbsp;</SPAN><CODE>db/schema.cds</CODE>:</P><PRE><CODE>namespace snakeladder; // โ”€โ”€ Status enum โ€” the heart of the state machine โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ type TurnStatus : String enum { Waiting; // Session created, not yet started Playing; // It's this player's turn โ€” roll the dice! Moving; // Dice rolled, player token is being moved Blocked; // Player hit a double-headed snake โ€” skips next turn Finished; // This player has reached square 100 and won } // โ”€โ”€ Board square โ€” pre-loaded with snake/ladder data โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ entity BoardSquares { key square : Integer; // 1โ€“100 snakeTo : Integer; // 0 if no snake on this square ladderTo : Integer; // 0 if no ladder on this square isDoubleHead: Boolean default false; // true if this is a double-headed snake mouth } // โ”€โ”€ Game session โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ entity GameSessions { key ID : UUID; name : String(100); // e.g. "Friday Afternoon Game" createdAt : Timestamp; startedAt : Timestamp; finishedAt : Timestamp; winner : Association to Players; } // โ”€โ”€ Player โ€” one row per player per session โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @flow.status: TurnStatus entity Players { key ID : UUID; session : Association to GameSessions; name : String(100); @readonly position : Integer default 1; // current square (1โ€“100) @readonly TurnStatus : TurnStatus default #Waiting; turnOrder : Integer; // 1 = goes first lastRoll : Integer; // last dice result }</CODE></PRE><BLOCKQUOTE dir="auto"><P class=""><STRONG>Why<SPAN>&nbsp;</SPAN><CODE>@flow.status</CODE><SPAN>&nbsp;</SPAN>on<SPAN>&nbsp;</SPAN><CODE>Players</CODE>?</STRONG><SPAN>&nbsp;</SPAN>Each player is an independent state machine. Their<SPAN>&nbsp;</SPAN><CODE>TurnStatus</CODE><SPAN>&nbsp;</SPAN>transitions independently โ€” one player is<SPAN>&nbsp;</SPAN><CODE>Playing</CODE><SPAN>&nbsp;</SPAN>while others are<SPAN>&nbsp;</SPAN><CODE>Waiting</CODE>.</P></BLOCKQUOTE><H3 id="3-create-the-service" id="toc-hId-376021817">3. Create the service</H3><P class="">Create<SPAN>&nbsp;</SPAN><CODE>srv/game-service.cds</CODE>:</P><PRE><CODE>using { snakeladder } from '../db/schema'; service GameService { entity GameSessions as projection on snakeladder.GameSessions; @flow.status: TurnStatus entity Players as projection on snakeladder.Players actions { // Week 2 will implement these โ€” declare them now so the model is complete @from: [ #Waiting ] @to: #Playing action startTurn(); @from: [ #Playing ] @to: #Moving action rollDice() returns Integer; @from: [ #Moving ] @to: #Waiting // transitions to next player in Week 3 action confirmMove(); @from: [ #Moving ] @to: #Blocked // double-headed snake bite action blockPlayer(); @from: [ #Blocked ] @to: $flow.previous // restores pre-block status action unblockPlayer(); @from: [ #Moving ] @to: #Finished action winGame(); } entity BoardSquares as projection on snakeladder.BoardSquares; }</CODE></PRE><H3 id="4-add-seed-data" id="toc-hId-179508312">4. Add seed data</H3><P class="">Create<SPAN>&nbsp;</SPAN><CODE>db/data/snakeladder-GameSessions.csv</CODE>:</P><PRE><CODE>ID,name,createdAt,startedAt,finishedAt,winner_ID a1b2c3d4-0001-0000-0000-000000000000,Demo Game,2026-07-01T09:00:00Z,,</CODE></PRE><P class="">Create<SPAN>&nbsp;</SPAN><CODE>db/data/snakeladder-BoardSquares.csv</CODE>:</P><PRE><CODE>square,snakeTo,ladderTo,isDoubleHead 1,0,0,false 2,0,38,false 3,0,0,false 4,0,0,false 5,0,0,false 6,0,0,false 7,0,0,false 8,0,31,false 9,0,0,false 10,0,0,false 11,0,0,false 12,0,0,false 13,0,0,false 14,0,0,false 15,0,26,false 16,0,0,false 17,0,0,false 18,0,0,false 19,0,0,false 20,0,0,false 21,0,42,false 22,0,0,false 23,0,0,false 24,0,0,false 25,0,0,false 26,0,0,false 27,0,0,false 28,0,84,false 29,0,0,false 30,0,0,false 31,0,0,false 32,0,0,false 33,0,0,false 34,0,0,false 35,0,0,false 36,0,44,false 37,0,0,false 38,0,0,false 39,0,0,false 40,3,0,true 41,0,0,false 42,0,0,false 43,18,0,false 44,0,0,false 45,0,0,false 46,0,0,false 47,3,0,true 48,0,0,false 49,0,0,false 50,0,0,false 51,0,67,false 52,0,0,false 53,0,0,false 54,34,0,true 55,0,0,false 56,0,0,false 57,0,0,false 58,0,0,false 59,0,0,false 60,0,0,false 61,0,0,false 62,0,0,false 63,34,0,true 64,0,0,false 65,0,0,false 66,0,0,false 67,0,0,false 68,0,0,false 69,0,0,false 70,0,0,false 71,0,91,false 72,50,0,true 73,0,0,false 74,0,0,false 75,0,0,false 76,20,0,false 77,0,0,false 78,0,98,false 79,0,0,false 80,0,0,false 81,0,0,false 82,0,0,false 83,0,0,false 84,0,0,false 85,0,0,false 86,0,0,false 87,0,0,false 88,0,0,false 89,50,0,true 90,0,0,false 91,0,0,false 92,0,0,false 93,0,0,false 94,0,0,false 95,0,0,false 96,0,0,false 97,0,0,false 98,0,0,false 99,7,0,false 100,0,0,false</CODE></PRE><BLOCKQUOTE dir="auto"><P class=""><STRONG>Double-headed snake logic:</STRONG><SPAN>&nbsp;</SPAN>Squares 40, 47, 54, 63, 72, and 89 all have<SPAN>&nbsp;</SPAN><CODE>isDoubleHead: true</CODE>. Pairs (40,47), (54,63), and (72,89) share a tail. Your Week 2 move handler will use this flag to apply the correct<SPAN>&nbsp;</SPAN><CODE>snakeTo</CODE><SPAN>&nbsp;</SPAN>destination.</P></BLOCKQUOTE><H3 id="5-run-and-verify" id="toc-hId--92236562">5. Run and verify</H3><PRE><CODE>cds watch</CODE></PRE><P class="">Open<SPAN>&nbsp;</SPAN><CODE><A href="http://localhost:4004" target="_blank" rel="noopener nofollow noreferrer">http://localhost:4004</A></CODE>. You should see<SPAN>&nbsp;</SPAN><CODE>GameService</CODE><SPAN>&nbsp;</SPAN>with endpoints for<SPAN>&nbsp;</SPAN><CODE>GameSessions</CODE>,<SPAN>&nbsp;</SPAN><CODE>Players</CODE>, and<SPAN>&nbsp;</SPAN><CODE>BoardSquares</CODE>.</P><P class="">Test the board data is loaded:</P><PRE><CODE>GET http://localhost:4004/odata/v4/GameService/BoardSquares?$filter=snakeTo gt 0</CODE></PRE><P class="">You should see all 9 snake-head squares returned.</P><HR /><H2 id="how-to-submit" id="toc-hId-4652940">How to Submit</H2><P class="">Reply to the<SPAN>&nbsp;</SPAN><STRONG>Week 1 blog post</STRONG><SPAN>&nbsp;</SPAN>on SAP Community with a comment containing:</P><OL class=""><LI>Your<SPAN>&nbsp;</SPAN><CODE>db/schema.cds</CODE><SPAN>&nbsp;</SPAN>โ€” paste as a CDS code block</LI><LI>Your<SPAN>&nbsp;</SPAN><CODE>srv/game-service.cds</CODE><SPAN>&nbsp;</SPAN>โ€” paste as a CDS code block</LI><LI>A screenshot of<SPAN>&nbsp;</SPAN><CODE><A href="http://localhost:4004" target="_blank" rel="noopener nofollow noreferrer">http://localhost:4004</A></CODE><SPAN>&nbsp;</SPAN>showing the<SPAN>&nbsp;</SPAN><CODE>GameService</CODE><SPAN>&nbsp;</SPAN>endpoints</LI><LI>One sentence explaining your choice of<SPAN>&nbsp;</SPAN><CODE>TurnStatus</CODE><SPAN>&nbsp;</SPAN>enum values โ€” did you add any of your own?</LI></OL><HR /><H2 id="whats-next" id="toc-hId--191860565">What's Next</H2><P class="">In<SPAN>&nbsp;</SPAN><STRONG>Week 2 โ€” Roll &amp; Move</STRONG>, you implement the bound actions. You'll write the<SPAN>&nbsp;</SPAN><CODE>rollDice</CODE><SPAN>&nbsp;</SPAN>handler, apply snake and ladder logic, and see CAP automatically enforce the<SPAN>&nbsp;</SPAN><CODE>@from</CODE><SPAN>&nbsp;</SPAN>/<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1392811">@TO</a></CODE><SPAN>&nbsp;</SPAN>transitions โ€” returning HTTP 409 when a player tries to roll out of turn.</P><H2 id="resources" id="toc-hId--388374070">Resources</H2><UL class=""><UL class=""><LI><A href="https://cap.cloud.sap/docs/guides/services/status-flows" target="_blank" rel="noopener nofollow noreferrer">CAP Status Flows Documentation</A></LI><LI><A href="https://cap.cloud.sap/docs/node.js/" target="_blank" rel="noopener nofollow noreferrer">CAP Node.js Documentation</A></LI><LI><A href="https://cap.cloud.sap/docs/get-started/#initial-setup" target="_blank" rel="nofollow noopener noreferrer">Initial Setup</A><SPAN>&nbsp;</SPAN></LI><LI><SPAN><A href="https://cap.cloud.sap/docs/tools/cds-cli#http" target="_self" rel="nofollow noopener noreferrer">cds-cli : cds add http</A>&nbsp;</SPAN></LI></UL></UL> 2026-07-01T15:14:58.896000+02:00 https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-week-2-roll-amp-move/ba-p/14438102 ๐Ÿ July Developer Challenge Week 2 โ€” Roll & Move 2026-07-12T20:29:00.014000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <H1 id="week-2--roll--move" id="toc-hId-1690169886">Week 2 โ€” Roll &amp; Move</H1><BLOCKQUOTE dir="auto"><P class=""><STRONG>Challenge series:</STRONG><SPAN>&nbsp;</SPAN><A href="https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-snake-amp-ladder-with-sap-cap-status-transition/ba-p/14429486" target="_self">Snake &amp; Ladder Developer Challenge</A><SPAN>&nbsp;</SPAN>| Week 2 of 4<SPAN>&nbsp;</SPAN><STRONG>Dates:</STRONG><SPAN>&nbsp;13</SPAN>โ€“19 July 2026</P></BLOCKQUOTE><HR /><H2 id="the-setup" id="toc-hId-1622739100">The Setup</H2><P class="">You have a board. You have a player state machine. This week we will make it move.</P><P class="">This week you implement the bound action handlers that power the game: dice rolling, position calculation, and snake/ladder logic. CAP's<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/39488">@flow</a>.status</CODE><SPAN>&nbsp;</SPAN>machinery will automatically enforce that moves only happen in the right state โ€” your job is to write the business logic inside each handler.</P><P class="">By the end of this week you can play a full single-player game from the command line using plain OData calls.</P><HR /><H2 id="what-youll-build-this-week" id="toc-hId-1426225595">What You'll Build This Week</H2><UL class=""><LI>Handler implementations for<SPAN>&nbsp;</SPAN><CODE>rollDice</CODE>,<SPAN>&nbsp;</SPAN><CODE>confirmMove</CODE>,<SPAN>&nbsp;</SPAN><CODE>blockPlayer</CODE>,<SPAN>&nbsp;</SPAN><CODE>unblockPlayer</CODE>, and<SPAN>&nbsp;</SPAN><CODE>winGame</CODE></LI><LI>Snake and ladder position resolution (including double-headed snake logic)</LI><LI>HTTP 409 proof: demonstrating that calling an action in the wrong state is rejected by CAP</LI><LI>An end-to-end single-player run reaching square 100</LI></UL><HR /><H2 id="how-cap-enforces-state-transitions" id="toc-hId-1229712090">How CAP Enforces State Transitions</H2><P class="">Before your handler code runs, CAP checks the current<SPAN>&nbsp;</SPAN><CODE>TurnStatus</CODE><SPAN>&nbsp;</SPAN>of the player entity against the<SPAN>&nbsp;</SPAN><CODE>@from</CODE><SPAN>&nbsp;</SPAN>annotation on the action. If the status doesn't match, CAP returns:</P><PRE><CODE><SPAN class="">HTTP/1.1</SPAN> <SPAN class="">409</SPAN> Conflict { "error": { "code": "409", "message": "Action 'rollDice' is not allowed in current status 'Waiting'" } }</CODE></PRE><P class="">No code from you. No<SPAN>&nbsp;</SPAN><CODE>if (status !== 'Playing') throw ...</CODE>. The annotation does it.</P><P class="">After your handler returns (without throwing), CAP updates the status field to the<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1392811">@TO</a></CODE><SPAN>&nbsp;</SPAN>value. Again โ€” no code from you.</P><P class="">This is the key insight of the week:<SPAN>&nbsp;</SPAN><STRONG>your handler only contains business logic, not state management</STRONG>.</P><HR /><H2 id="step-by-step" id="toc-hId-1033198585">Step-by-Step</H2><H3 id="1-create-the-service-implementation-file" id="toc-hId-965767799">1. Create the service implementation file</H3><P class="">Create<SPAN>&nbsp;</SPAN><CODE>srv/game-service.js</CODE>:</P><PRE><CODE><SPAN class="">const</SPAN> cds = <SPAN class="">require</SPAN>(<SPAN class="">'@sap/cds'</SPAN>) <SPAN class="">module</SPAN>.<SPAN class="">exports</SPAN> = <SPAN class="">class</SPAN> <SPAN class="">GameService</SPAN> <SPAN class="">extends</SPAN> <SPAN class="">cds.ApplicationService</SPAN> { <SPAN class="">async</SPAN> <SPAN class="">init</SPAN>() { <SPAN class="">const</SPAN> { <SPAN class="">Players</SPAN>, <SPAN class="">BoardSquares</SPAN> } = <SPAN class="">this</SPAN>.<SPAN class="">entities</SPAN> <SPAN class="">// โ”€โ”€ startTurn โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€</SPAN> <SPAN class="">// @from: [#Waiting] โ†’ CAP validates status before this runs</SPAN> <SPAN class="">// @to: #Playing โ†’ CAP updates status after this returns</SPAN> <SPAN class="">// No business logic needed โ€” CAP's @flow.status handles the transition.</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'startTurn'</SPAN>, <SPAN class="">'Players'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">// Intentionally empty: the @from/@to annotations do all the work.</SPAN> }) <SPAN class="">// โ”€โ”€ rollDice โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€</SPAN> <SPAN class="">// @from: [#Playing] โ†’ CAP validates status before this runs</SPAN> <SPAN class="">// @to: #Moving โ†’ CAP updates status after this returns</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'rollDice'</SPAN>, <SPAN class="">'Players'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">const</SPAN> player = <SPAN class="">await</SPAN> <SPAN class="">SELECT</SPAN>.<SPAN class="">one</SPAN>.<SPAN class="">from</SPAN>(<SPAN class="">Players</SPAN>).<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: req.<SPAN class="">params</SPAN>[<SPAN class="">0</SPAN>].<SPAN class="">ID</SPAN> }) <SPAN class="">if</SPAN> (!player) <SPAN class="">return</SPAN> req.<SPAN class="">error</SPAN>(<SPAN class="">404</SPAN>, <SPAN class="">'Player not found'</SPAN>) <SPAN class="">const</SPAN> roll = <SPAN class="">Math</SPAN>.<SPAN class="">ceil</SPAN>(<SPAN class="">Math</SPAN>.<SPAN class="">random</SPAN>() * <SPAN class="">6</SPAN>) <SPAN class="">let</SPAN> newPosition = player.<SPAN class="">position</SPAN> + roll <SPAN class="">// Can't overshoot square 100 โ€” must land exactly</SPAN> <SPAN class="">if</SPAN> (newPosition &gt; <SPAN class="">100</SPAN>) { <SPAN class="">await</SPAN> <SPAN class="">UPDATE</SPAN>(<SPAN class="">Players</SPAN>).<SPAN class="">set</SPAN>({ <SPAN class="">lastRoll</SPAN>: roll }).<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: player.<SPAN class="">ID</SPAN> }) <SPAN class="">const</SPAN> needed = <SPAN class="">100</SPAN> - player.<SPAN class="">position</SPAN> <SPAN class="">return</SPAN> req.<SPAN class="">reject</SPAN>(<SPAN class="">409</SPAN>, <SPAN class="">`Need exactly <SPAN class="">${needed}</SPAN> to finish. Rolled <SPAN class="">${roll}</SPAN>. No move.`</SPAN>) } <SPAN class="">// Resolve snake or ladder on the new square</SPAN> <SPAN class="">const</SPAN> square = <SPAN class="">await</SPAN> <SPAN class="">SELECT</SPAN>.<SPAN class="">one</SPAN>.<SPAN class="">from</SPAN>(<SPAN class="">BoardSquares</SPAN>).<SPAN class="">where</SPAN>({ <SPAN class="">square</SPAN>: newPosition }) <SPAN class="">let</SPAN> finalPosition = newPosition <SPAN class="">let</SPAN> event = <SPAN class="">null</SPAN> <SPAN class="">if</SPAN> (square?.<SPAN class="">ladderTo</SPAN>) { finalPosition = square.<SPAN class="">ladderTo</SPAN> event = { <SPAN class="">type</SPAN>: <SPAN class="">'ladder'</SPAN>, <SPAN class="">'from'</SPAN>: newPosition, <SPAN class="">'to'</SPAN>: finalPosition } } <SPAN class="">else</SPAN> <SPAN class="">if</SPAN> (square?.<SPAN class="">snakeTo</SPAN>) { finalPosition = square.<SPAN class="">snakeTo</SPAN> event = square.<SPAN class="">isDoubleHead</SPAN> ? { <SPAN class="">type</SPAN>: <SPAN class="">'doubleSnake'</SPAN>, <SPAN class="">'from'</SPAN>: newPosition, <SPAN class="">'to'</SPAN>: finalPosition } : { <SPAN class="">type</SPAN>: <SPAN class="">'snake'</SPAN>, <SPAN class="">'from'</SPAN>: newPosition, <SPAN class="">'to'</SPAN>: finalPosition } } <SPAN class="">// Persist position and last roll</SPAN> <SPAN class="">await</SPAN> <SPAN class="">UPDATE</SPAN>(<SPAN class="">Players</SPAN>) .<SPAN class="">set</SPAN>({ <SPAN class="">position</SPAN>: finalPosition, <SPAN class="">lastRoll</SPAN>: roll }) .<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: player.<SPAN class="">ID</SPAN> }) <SPAN class="">// Emit domain event so subscribers (Week 3) can react</SPAN> <SPAN class="">if</SPAN> (event) <SPAN class="">await</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">emit</SPAN>(<SPAN class="">'BoardEvent'</SPAN>, { <SPAN class="">playerID</SPAN>: player.<SPAN class="">ID</SPAN>, ...event }) <SPAN class="">// Use req.reply() โ€” not return โ€” to pin the response value before CAP's</SPAN> <SPAN class="">// @flow.status after-handler runs. A plain `return` gets cleared by the</SPAN> <SPAN class="">// flow machinery, producing HTTP 204 instead of the expected 200 + body.</SPAN> req.<SPAN class="">reply</SPAN>({ roll, <SPAN class="">position</SPAN>: finalPosition, <SPAN class="">event</SPAN>: event?.<SPAN class="">type</SPAN> ?? <SPAN class="">'normal'</SPAN> }) }) <SPAN class="">// โ”€โ”€ confirmMove โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€</SPAN> <SPAN class="">// @from: [#Moving] โ†’ normal end of a turn, hands off to next player (Week 3)</SPAN> <SPAN class="">// @to: #Waiting โ†’ player waits for their next turn</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'confirmMove'</SPAN>, <SPAN class="">'Players'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">// Week 3 will add turn rotation here.</SPAN> <SPAN class="">// For now: just let CAP apply @to: #Waiting</SPAN> }) <SPAN class="">// โ”€โ”€ blockPlayer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€</SPAN> <SPAN class="">// @from: [#Moving] โ†’ called when player lands on a double-headed snake</SPAN> <SPAN class="">// @to: #Blocked โ†’ player skips their next turn</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'blockPlayer'</SPAN>, <SPAN class="">'Players'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">// Business logic: the position was already updated in rollDice.</SPAN> <SPAN class="">// This action just transitions state โ€” CAP handles the @to: #Blocked update.</SPAN> req.<SPAN class="">info</SPAN>(<SPAN class="">'Player is blocked and will skip their next turn.'</SPAN>) }) <SPAN class="">// โ”€โ”€ unblockPlayer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€</SPAN> <SPAN class="">// @from: [#Blocked] โ†’ called at the start of a blocked player's next turn</SPAN> <SPAN class="">// @to: $flow.previous โ†’ CAP restores the status to whatever it was before #Blocked</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'unblockPlayer'</SPAN>, <SPAN class="">'Players'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">// CAP's $flow.previous does the work โ€” no code needed here.</SPAN> <SPAN class="">// The player's status returns to #Waiting automatically.</SPAN> }) <SPAN class="">// โ”€โ”€ winGame โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€</SPAN> <SPAN class="">// @from: [#Moving] โ†’ called when player.position === 100</SPAN> <SPAN class="">// @to: #Finished โ†’ player has won</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'winGame'</SPAN>, <SPAN class="">'Players'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">const</SPAN> player = <SPAN class="">await</SPAN> <SPAN class="">SELECT</SPAN>.<SPAN class="">one</SPAN>.<SPAN class="">from</SPAN>(<SPAN class="">Players</SPAN>).<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: req.<SPAN class="">params</SPAN>[<SPAN class="">0</SPAN>].<SPAN class="">ID</SPAN> }) <SPAN class="">if</SPAN> (!player) <SPAN class="">return</SPAN> req.<SPAN class="">error</SPAN>(<SPAN class="">404</SPAN>, <SPAN class="">'Player not found'</SPAN>) <SPAN class="">// Mark the session as finished with this player as winner</SPAN> <SPAN class="">await</SPAN> <SPAN class="">UPDATE</SPAN>(<SPAN class="">'snakeladder.GameSessions'</SPAN>) .<SPAN class="">set</SPAN>({ <SPAN class="">finishedAt</SPAN>: <SPAN class="">new</SPAN> <SPAN class="">Date</SPAN>().<SPAN class="">toISOString</SPAN>(), <SPAN class="">winner_ID</SPAN>: player.<SPAN class="">ID</SPAN> }) .<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: player.<SPAN class="">session_ID</SPAN> }) <SPAN class="">await</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">emit</SPAN>(<SPAN class="">'GameWon'</SPAN>, { <SPAN class="">playerID</SPAN>: player.<SPAN class="">ID</SPAN>, <SPAN class="">sessionID</SPAN>: player.<SPAN class="">session_ID</SPAN> }) }) <SPAN class="">return</SPAN> <SPAN class="">super</SPAN>.<SPAN class="">init</SPAN>() } }</CODE></PRE><P class="">If you notice <CODE><SPAN class="">rollDice</SPAN></CODE><SPAN>&nbsp;action response looks different from how it was defined last week. </SPAN></P><P class=""><SPAN>Lets adapt it as shown in srv/game-service.cds</SPAN></P><PRE><CODE> @from: [ #Playing ] @to: #Moving action rollDice() returns { roll: Integer; position: Integer; event: String; };</CODE></PRE><H3 id="toc-hId-769254294">&nbsp;</H3><H3 id="2-add-the-domain-events-to-your-service-cds" id="toc-hId-572740789">2. Add the domain events to your service CDS</H3><P class="">Add these to the bottom of<SPAN>&nbsp;</SPAN><CODE>srv/game-service.cds</CODE>:</P><PRE><CODE>// Domain events โ€” emitted by handlers, consumed in Week 3 event BoardEvent { playerID : UUID; type : String; // 'ladder' | 'snake' | 'doubleSnake' from : Integer; // 'from' is a CDS reserved word โ€” escape with ![] ![to] : Integer; } event GameWon { playerID : UUID; sessionID : UUID; }</CODE></PRE><P class="">An error is intentionally introduced in the snippet above with the hint.&nbsp;&nbsp;</P><H3 id="3-test-the-state-machine-manually" id="toc-hId-376227284">3. Test the state machine manually</H3><P class="">Start the server:</P><PRE><CODE>cds watch</CODE></PRE><P class="">First, create a player using the REST API or the Fiori Launchpad at<SPAN>&nbsp;</SPAN><CODE><A href="http://localhost:4004" target="_blank" rel="noopener nofollow noreferrer">http://localhost:4004</A></CODE>. Then test the status flow:</P><BLOCKQUOTE dir="auto"><P class=""><STRONG>Note on service path:</STRONG><SPAN>&nbsp;</SPAN>CAP derives the OData path from the service name.<SPAN>&nbsp;</SPAN><CODE>GameService</CODE><SPAN>&nbsp;</SPAN>maps to<SPAN>&nbsp;</SPAN><CODE>/odata/v4/game</CODE><SPAN>&nbsp;</SPAN>(lowercase, stripped of the "Service" suffix). All example requests below use this path. You can confirm the mounted path in the<SPAN>&nbsp;</SPAN><CODE>cds watch</CODE><SPAN>&nbsp;</SPAN>console output:<SPAN>&nbsp;</SPAN><CODE>serving GameService { at: '/odata/v4/game' }</CODE>.</P></BLOCKQUOTE><P class=""><STRONG>Step 1 โ€” Start a turn (transitions Waiting โ†’ Playing):</STRONG></P><PRE><CODE>POST http://localhost:4004/odata/v4/game/Players(ID=&lt;your-player-id&gt;)/startTurn <SPAN class="">Content-Type</SPAN><SPAN class="">: </SPAN>application/json {}</CODE></PRE><P class="">Expected response:<SPAN>&nbsp;</SPAN><STRONG>HTTP 204 No Content</STRONG><SPAN>&nbsp;</SPAN>(empty body). This is correct โ€”<SPAN>&nbsp;</SPAN><CODE>startTurn</CODE><SPAN>&nbsp;</SPAN>has no return type, so CAP applies the<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1392811">@TO</a>: #Playing</CODE><SPAN>&nbsp;</SPAN>transition silently. Verify the state changed with a GET:</P><PRE><CODE>GET http://localhost:4004/odata/v4/game/Players(ID=&lt;your-player-id&gt;)?$select=TurnStatus</CODE></PRE><P class="">You should see<SPAN>&nbsp;</SPAN><CODE>"TurnStatus": "Playing"</CODE>.</P><P class=""><STRONG>Step 2 โ€” Roll the dice (transitions Playing โ†’ Moving):</STRONG></P><PRE><CODE>POST http://localhost:4004/odata/v4/game/Players(ID=&lt;your-player-id&gt;)/rollDice <SPAN class="">Content-Type</SPAN><SPAN class="">: </SPAN>application/json {}</CODE></PRE><P class="">Expected response:<SPAN>&nbsp;</SPAN><STRONG>HTTP 200</STRONG><SPAN>&nbsp;</SPAN>with the dice roll, new position, and board event:</P><PRE><CODE><SPAN class="">{</SPAN> <SPAN class="">"@context"</SPAN><SPAN class="">:</SPAN> <SPAN class="">"$metadata#GameService.rollDiceType"</SPAN><SPAN class="">,</SPAN> <SPAN class="">"roll"</SPAN><SPAN class="">:</SPAN> <SPAN class="">4</SPAN><SPAN class="">,</SPAN> <SPAN class="">"position"</SPAN><SPAN class="">:</SPAN> <SPAN class="">38</SPAN><SPAN class="">,</SPAN> <SPAN class="">"event"</SPAN><SPAN class="">:</SPAN> <SPAN class="">"ladder"</SPAN> <SPAN class="">}</SPAN> </CODE></PRE><P class="">The<SPAN>&nbsp;</SPAN><CODE>event</CODE><SPAN>&nbsp;</SPAN>field is<SPAN>&nbsp;</SPAN><CODE>"normal"</CODE>,<SPAN>&nbsp;</SPAN><CODE>"ladder"</CODE>,<SPAN>&nbsp;</SPAN><CODE>"snake"</CODE>, or<SPAN>&nbsp;</SPAN><CODE>"doubleSnake"</CODE>. The<SPAN>&nbsp;</SPAN><CODE>position</CODE><SPAN>&nbsp;</SPAN>is the<SPAN>&nbsp;</SPAN><STRONG>final square after any snake or ladder has been applied</STRONG><SPAN>&nbsp;</SPAN>โ€” so you can read the outcome directly from the action response without a follow-up GET.</P><BLOCKQUOTE dir="auto"><P class=""><STRONG>Why<SPAN>&nbsp;</SPAN><CODE>req.reply()</CODE><SPAN>&nbsp;</SPAN>not<SPAN>&nbsp;</SPAN><CODE>return</CODE>?</STRONG><SPAN>&nbsp;</SPAN>CAP's<SPAN>&nbsp;</SPAN><CODE>@flow.status</CODE><SPAN>&nbsp;</SPAN>machinery runs an after-handler that updates the status field. If you use a plain<SPAN>&nbsp;</SPAN><CODE>return</CODE>, that after-handler clears the response value and the client gets<SPAN>&nbsp;</SPAN><CODE>HTTP 204 No Content</CODE><SPAN>&nbsp;</SPAN>instead. Calling<SPAN>&nbsp;</SPAN><CODE>req.reply({...})</CODE><SPAN>&nbsp;</SPAN>pins the response before the flow handler runs, so the body reaches the client.</P></BLOCKQUOTE><P class=""><STRONG>Step 3 โ€” Prove the 409 rejection:</STRONG></P><P class="">Without calling<SPAN>&nbsp;</SPAN><CODE>confirmMove</CODE><SPAN>&nbsp;</SPAN>first, try to call<SPAN>&nbsp;</SPAN><CODE>rollDice</CODE><SPAN>&nbsp;</SPAN>again. CAP will return:</P><PRE><CODE>HTTP/<SPAN class="">1.1</SPAN> <SPAN class="">409</SPAN> Conflict <SPAN class="">{</SPAN> <SPAN class="">"error"</SPAN><SPAN class="">:</SPAN> <SPAN class="">{</SPAN> <SPAN class="">"code"</SPAN><SPAN class="">:</SPAN> <SPAN class="">"409"</SPAN><SPAN class="">,</SPAN> <SPAN class="">"message"</SPAN><SPAN class="">:</SPAN> <SPAN class="">"Action 'rollDice' is not allowed in current status 'Moving'"</SPAN> <SPAN class="">}</SPAN> <SPAN class="">}</SPAN> </CODE></PRE><P class="">This is the framework working exactly as designed. Screenshot this โ€” it's your Week 2 submission evidence.</P><P class=""><STRONG>Step 4 โ€” Confirm the move (transitions Moving โ†’ Waiting):</STRONG></P><PRE><CODE>POST http://localhost:4004/odata/v4/game/Players(ID=&lt;your-player-id&gt;)/confirmMove <SPAN class="">Content-Type</SPAN><SPAN class="">: </SPAN>application/json {}</CODE></PRE><P class="">Expected response:<SPAN>&nbsp;</SPAN><STRONG>HTTP 204 No Content</STRONG><SPAN>&nbsp;</SPAN>(empty body).<SPAN>&nbsp;</SPAN><CODE>confirmMove</CODE><SPAN>&nbsp;</SPAN>has no return type โ€” the empty response is correct. Verify with a GET that<SPAN>&nbsp;</SPAN><CODE>TurnStatus</CODE><SPAN>&nbsp;</SPAN>is now<SPAN>&nbsp;</SPAN><CODE>"Waiting"</CODE>.</P><H3 id="4-test-the-double-headed-snake" id="toc-hId-179713779">4. Test the double-headed snake</H3><P class="">Manually set a player to position 88 (one roll away from square 89, a double-headed snake head):</P><PRE><CODE>PATCH http://localhost:4004/odata/v4/game/Players(ID=&lt;your-player-id&gt;) <SPAN class="">Content-Type</SPAN><SPAN class="">: </SPAN>application/json { "position": 88 }</CODE></PRE><BLOCKQUOTE dir="auto"><P class="">Note:<SPAN>&nbsp;</SPAN><CODE>position</CODE><SPAN>&nbsp;</SPAN>is<SPAN>&nbsp;</SPAN><CODE>@readonly</CODE><SPAN>&nbsp;</SPAN>in the service โ€” for testing, update it directly via the db entity or temporarily remove<SPAN>&nbsp;</SPAN><CODE>@readonly</CODE>.</P><P class=""><SPAN>remove&nbsp;</SPAN><CODE>@readonly</CODE><SPAN>&nbsp;from&nbsp;</SPAN><CODE>srv/game-service.cds</CODE><SPAN>&nbsp;for testing, and note that patching&nbsp;</SPAN><CODE>@readonly</CODE><SPAN>&nbsp;fields fails silently with HTTP 200.</SPAN></P></BLOCKQUOTE><P class="">Then start a turn and roll โ€” if you roll 1, you land on 89 (a double-headed snake head). Your position should move to 50 (the shared tail). The<SPAN>&nbsp;</SPAN><CODE>BoardEvent</CODE><SPAN>&nbsp;</SPAN>event should fire with<SPAN>&nbsp;</SPAN><CODE>type: 'doubleSnake'</CODE>.</P><P class="">Follow up by calling<SPAN>&nbsp;</SPAN><CODE>blockPlayer</CODE><SPAN>&nbsp;</SPAN>to transition to<SPAN>&nbsp;</SPAN><CODE>#Blocked</CODE>, then<SPAN>&nbsp;</SPAN><CODE>unblockPlayer</CODE><SPAN>&nbsp;</SPAN>to see<SPAN>&nbsp;</SPAN><CODE>$flow.previous</CODE><SPAN>&nbsp;</SPAN>restore the state.</P><HR /><H2 id="the-flowprevious-moment" id="toc-hId-201371912">The<SPAN>&nbsp;</SPAN><CODE>$flow.previous</CODE><SPAN>&nbsp;</SPAN>Moment</H2><P class="">When you call<SPAN>&nbsp;</SPAN><CODE>unblockPlayer</CODE><SPAN>&nbsp;</SPAN>and see the status return to its previous value automatically โ€” that's CAP's state history at work. No code, no stored "previousStatus" field. The framework tracks it.</P><BLOCKQUOTE dir="auto"><P class=""><STRONG>Note:</STRONG><SPAN>&nbsp;</SPAN><CODE>$flow.previous</CODE><SPAN>&nbsp;</SPAN>restores whatever status the entity held<SPAN>&nbsp;</SPAN><EM>before</EM><SPAN>&nbsp;</SPAN>the transition to<SPAN>&nbsp;</SPAN><CODE>#Blocked</CODE>. In this flow,<SPAN>&nbsp;</SPAN><CODE>blockPlayer</CODE><SPAN>&nbsp;</SPAN>is called from<SPAN>&nbsp;</SPAN><CODE>#Moving</CODE>, so<SPAN>&nbsp;</SPAN><CODE>unblockPlayer</CODE><SPAN>&nbsp;</SPAN>restores<SPAN>&nbsp;</SPAN><CODE>#Moving</CODE><SPAN>&nbsp;</SPAN>โ€” not<SPAN>&nbsp;</SPAN><CODE>#Waiting</CODE>. The game loop then calls<SPAN>&nbsp;</SPAN><CODE>confirmMove</CODE><SPAN>&nbsp;</SPAN>to finish the turn. If you call<SPAN>&nbsp;</SPAN><CODE>blockPlayer</CODE><SPAN>&nbsp;</SPAN>from a different status in future extensions,<SPAN>&nbsp;</SPAN><CODE>$flow.previous</CODE><SPAN>&nbsp;</SPAN>will restore that one instead.</P></BLOCKQUOTE><HR /><H2 id="stretch-goal" id="toc-hId-4858407">Stretch Goal</H2><P class="">Add a<SPAN>&nbsp;</SPAN><CODE>skipTurn</CODE><SPAN>&nbsp;</SPAN>action that can be called when a player is<SPAN>&nbsp;</SPAN><CODE>#Blocked</CODE>, as an alternative to<SPAN>&nbsp;</SPAN><CODE>unblockPlayer</CODE>, that uses<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1392811">@TO</a>: $flow.previous</CODE><SPAN>&nbsp;</SPAN>as well but also increments a<SPAN>&nbsp;</SPAN><CODE>turnsBlocked</CODE><SPAN>&nbsp;</SPAN>counter on the player:</P><PRE><CODE>@from: [ #Blocked ] @to: $flow.previous action skipTurn();</CODE></PRE><PRE><CODE><SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'skipTurn'</SPAN>, <SPAN class="">'Players'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">const</SPAN> player = <SPAN class="">await</SPAN> <SPAN class="">SELECT</SPAN>.<SPAN class="">one</SPAN>.<SPAN class="">from</SPAN>(<SPAN class="">Players</SPAN>).<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: req.<SPAN class="">params</SPAN>[<SPAN class="">0</SPAN>].<SPAN class="">ID</SPAN> }) <SPAN class="">await</SPAN> <SPAN class="">UPDATE</SPAN>(<SPAN class="">Players</SPAN>) .<SPAN class="">set</SPAN>({ <SPAN class="">turnsBlocked</SPAN>: (player.<SPAN class="">turnsBlocked</SPAN> || <SPAN class="">0</SPAN>) + <SPAN class="">1</SPAN> }) .<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: player.<SPAN class="">ID</SPAN> }) })</CODE></PRE><HR /><H2 id="how-to-submit" id="toc-hId--191655098">How to Submit</H2><P class="">Reply to the<SPAN>&nbsp;</SPAN><STRONG>Week 2 blog post</STRONG><SPAN>&nbsp;</SPAN>on SAP Community with a comment containing:</P><OL class=""><LI>Your<SPAN>&nbsp;</SPAN><CODE>srv/game-service.js</CODE><SPAN>&nbsp;</SPAN>handler implementations (paste as a JS code block)</LI><LI>A screenshot showing the<SPAN>&nbsp;</SPAN><STRONG>HTTP 409 response</STRONG><SPAN>&nbsp;</SPAN>when calling<SPAN>&nbsp;</SPAN><CODE>rollDice</CODE><SPAN>&nbsp;</SPAN>in the wrong state</LI><LI>A screenshot or OData response showing your player's<SPAN>&nbsp;</SPAN><CODE>position</CODE><SPAN>&nbsp;</SPAN>after landing on a snake or ladder</LI><LI>One sentence: what happens if you call<SPAN>&nbsp;</SPAN><CODE>winGame</CODE><SPAN>&nbsp;</SPAN>on a player whose<SPAN>&nbsp;</SPAN><CODE>position</CODE><SPAN>&nbsp;</SPAN>is not 100? How does your implementation handle it?</LI></OL><HR /><H2 id="whats-next" id="toc-hId--388168603">What's Next</H2><P class="">In<SPAN>&nbsp;</SPAN><STRONG>Week 3 โ€” Multiplayer &amp; Events</STRONG>, you add a second player, implement turn rotation, and wire up a CAP event subscriber that automatically advances the game state when a move is confirmed.</P> 2026-07-12T20:29:00.014000+02:00 https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-week-3-multiplayer-amp-events/ba-p/14444431 ๐Ÿ July Developer Challenge Week 3 โ€” Multiplayer & Events 2026-07-20T13:35:35.720000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <H1 id="week-3--multiplayer--events" id="toc-hId-1690977218">Week 3 โ€” Multiplayer &amp; Events</H1><BLOCKQUOTE dir="auto"><P class=""><STRONG>Challenge series:</STRONG><SPAN>&nbsp;</SPAN><A href="https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-snake-amp-ladder-with-sap-cap-status-transition/ba-p/14429486" target="_self">Snake &amp; Ladder Developer Challenge</A><SPAN>&nbsp;</SPAN>| Week 3 of 4<SPAN>&nbsp;</SPAN><STRONG>Dates:</STRONG><SPAN>&nbsp;20</SPAN>โ€“26 July 2026</P></BLOCKQUOTE><HR /><H2 id="the-setup" id="toc-hId-1623546432">The Setup</H2><P class="">A single-player Snake &amp; Ladder game is a solitaire puzzle. The real game is multiplayer โ€” taking turns, watching opponents climb ladders, celebrating when a snake bites someone ahead of you.</P><P class="">This week you add a second player, implement turn rotation, and wire CAP events so the game state advances automatically. You'll also use the<SPAN>&nbsp;</SPAN><STRONG>CAP MCP server</STRONG><SPAN>&nbsp;</SPAN>to let an AI assistant watch the game โ€” describing what happened on each turn in natural language.</P><HR /><H2 id="what-youll-build-this-week" id="toc-hId-1427032927">What You'll Build This Week</H2><UL class=""><LI>Turn rotation:<SPAN>&nbsp;</SPAN><CODE>confirmMove</CODE><SPAN>&nbsp;</SPAN>activates the next player automatically</LI><LI>A<SPAN>&nbsp;</SPAN><CODE>GameSession</CODE><SPAN>&nbsp;</SPAN>state machine (separate from<SPAN>&nbsp;</SPAN><CODE>Player</CODE><SPAN>&nbsp;</SPAN>status)</LI><LI>A CAP event subscriber that drives turn handoff</LI><LI>A<SPAN>&nbsp;</SPAN><CODE>TurnLog</CODE><SPAN>&nbsp;</SPAN>entity recording every move for replay</LI><LI>CAP MCP server connected to the game โ€” so an AI can query board state and narrate moves</LI></UL><HR /><H2 id="background-cap-events-as-the-turn-engine" id="toc-hId-1230519422">Background: CAP Events as the Turn Engine</H2><P class="">CAP events let services communicate without tight coupling. Your<SPAN>&nbsp;</SPAN><CODE>GameService</CODE><SPAN>&nbsp;</SPAN>emits a<SPAN>&nbsp;</SPAN><CODE>BoardEvent</CODE><SPAN>&nbsp;</SPAN>when a player moves. A subscriber โ€” in the same service or a separate one โ€” reacts to that event and advances the game.</P><BLOCKQUOTE dir="auto"><P class=""><STRONG>CAP messaging key facts :</STRONG></P><UL class=""><LI><CODE>this.emit(eventName, data)</CODE><SPAN>&nbsp;</SPAN>inside an action handler automatically fires<SPAN>&nbsp;</SPAN><STRONG>after</STRONG><SPAN>&nbsp;</SPAN>the DB transaction commits โ€” no manual outbox needed for in-process events.</LI><LI>This week uses<SPAN>&nbsp;</SPAN><STRONG>in-process eventing</STRONG><SPAN>&nbsp;</SPAN>(same<SPAN>&nbsp;</SPAN><CODE>cds watch</CODE><SPAN>&nbsp;</SPAN>process). If you later split into microservices, add<SPAN>&nbsp;</SPAN><CODE>file-based-messaging</CODE><SPAN>&nbsp;</SPAN>to<SPAN>&nbsp;</SPAN><CODE>package.json</CODE><SPAN>&nbsp;</SPAN>and your handler code stays unchanged โ€” only the config changes.</LI></UL></BLOCKQUOTE><P class="">This is the architecture for multiplayer:</P><PRE><CODE>Player calls rollDice โ†’ CAP validates @from: [#Playing] โ†’ Handler runs: calculates new position, emits BoardEvent โ†’ CAP applies @to: #Moving โ†’ Player calls confirmMove โ†’ CAP validates @from: [#Moving] โ†’ Handler emits TurnComplete event โ†’ CAP applies @to: #Waiting โ†’ TurnComplete subscriber: logs the turn (next-player activation already done inside confirmMove)</CODE></PRE><P class="">No polling. No<SPAN>&nbsp;</SPAN><CODE>setInterval</CODE>. The event graph drives the game.</P><HR /><H2 id="step-by-step" id="toc-hId-1034005917">Step-by-Step</H2><H3 id="0-upgrade-to-the-latest-cap-version" id="toc-hId-966575131">0. Upgrade to the latest CAP version</H3><P class="">Before adding Week 3 features, upgrade your project to the latest<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/2302137">@SAP</a>/cds</CODE>. CAP 10 brings performance improvements, refined flow-status behaviour, and updated MCP support.</P><BLOCKQUOTE dir="auto"><P class=""><STRONG>If you skipped Week 2's CDS 10 note:</STRONG><SPAN>&nbsp;</SPAN><CODE>cds init --nodejs</CODE><SPAN>&nbsp;</SPAN>in CDS 10 generates<SPAN>&nbsp;</SPAN><CODE>"type": "module"</CODE><SPAN>&nbsp;</SPAN>in<SPAN>&nbsp;</SPAN><CODE>package.json</CODE>. The handler code in these guides uses CommonJS<SPAN>&nbsp;</SPAN><CODE>require()</CODE>. If your server crashes with<SPAN>&nbsp;</SPAN><CODE>ReferenceError: require is not defined</CODE>, remove<SPAN>&nbsp;</SPAN><CODE>"type": "module"</CODE><SPAN>&nbsp;</SPAN>from<SPAN>&nbsp;</SPAN><CODE>package.json</CODE>. Running<SPAN>&nbsp;</SPAN><CODE>npm install @sap/cds@latest</CODE><SPAN>&nbsp;</SPAN>also removes it automatically.</P></BLOCKQUOTE><PRE><CODE>npm install @sap/cds@latest @cap-js/mcp@latest @cap-js/sqlite@latest</CODE></PRE><P class="">Verify the versions installed:</P><PRE><CODE>cds --version</CODE></PRE><P class="">You should see<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/2302137">@SAP</a>/cds</CODE><SPAN>&nbsp;</SPAN>at<SPAN>&nbsp;</SPAN><STRONG>10.x</STRONG><SPAN>&nbsp;</SPAN>or higher. Restart<SPAN>&nbsp;</SPAN><CODE>cds watch</CODE><SPAN>&nbsp;</SPAN>โ€” if the service starts cleanly, you're ready.</P><BLOCKQUOTE dir="auto"><P class=""><STRONG>Global CLI:</STRONG><SPAN>&nbsp;</SPAN>The<SPAN>&nbsp;</SPAN><CODE>cds</CODE><SPAN>&nbsp;</SPAN>command comes from<SPAN>&nbsp;</SPAN><CODE>@sap/cds-dk</CODE>. Keep it in sync:</P><PRE><CODE>npm install -g @sap/cds-dk@latest</CODE></PRE></BLOCKQUOTE><HR /><H3 id="1-add-the-turnlog-entity" id="toc-hId-770061626">1. Add the<SPAN>&nbsp;</SPAN><CODE>TurnLog</CODE><SPAN>&nbsp;</SPAN>entity</H3><P class="">Add to<SPAN>&nbsp;</SPAN><CODE>db/schema.cds</CODE>:</P><PRE><CODE>// โ”€โ”€ Turn log โ€” full history of every move โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ entity TurnLog { key ID : UUID; session : Association to GameSessions; player : Association to Players; turnNumber : Integer; diceRoll : Integer; fromSquare : Integer; toSquare : Integer; eventType : String(20); // 'normal' | 'ladder' | 'snake' | 'doubleSnake' | 'win' | 'blocked' timestamp : Timestamp; }</CODE></PRE><BLOCKQUOTE dir="auto"><P class=""><STRONG>Also update the<SPAN>&nbsp;</SPAN><CODE>Players</CODE><SPAN>&nbsp;</SPAN>entity</STRONG><SPAN>&nbsp;</SPAN>in<SPAN>&nbsp;</SPAN><CODE>db/schema.cds</CODE><SPAN>&nbsp;</SPAN>to add these two fields needed by the<SPAN>&nbsp;</SPAN><CODE>confirmMove</CODE><SPAN>&nbsp;</SPAN>handler:</P><PRE><CODE>prevPosition : Integer default 1; // position before last dice roll lastEventType : String(20); // event from last roll: normal|ladder|snake|doubleSnake</CODE></PRE></BLOCKQUOTE><P class="">Add it to<SPAN>&nbsp;</SPAN><CODE>srv/game-service.cds</CODE><SPAN>&nbsp;</SPAN>โ€”<SPAN>&nbsp;</SPAN><STRONG>inside</STRONG><SPAN>&nbsp;</SPAN>the<SPAN>&nbsp;</SPAN><CODE>service GameService { }</CODE><SPAN>&nbsp;</SPAN>block, before the closing brace:</P><PRE><CODE>entity TurnLog as projection on snakeladder.TurnLog;</CODE></PRE><P class="">And add the<SPAN>&nbsp;</SPAN><CODE>TurnComplete</CODE><SPAN>&nbsp;</SPAN>event โ€” also<SPAN>&nbsp;</SPAN><STRONG>inside</STRONG><SPAN>&nbsp;</SPAN>the service block so it appears in OData<SPAN>&nbsp;</SPAN><CODE>$metadata</CODE><SPAN>&nbsp;</SPAN>and the MCP<SPAN>&nbsp;</SPAN><CODE>describe</CODE><SPAN>&nbsp;</SPAN>tool:</P><PRE><CODE>event TurnComplete { sessionID : UUID; playerID : UUID; nextPlayerID : UUID; turnNumber : Integer; }</CODE></PRE><H3 id="2-implement-turn-rotation-in-the-handler" id="toc-hId-573548121">2. Implement turn rotation in the handler</H3><P class="">Update<SPAN>&nbsp;</SPAN><CODE>srv/game-service.js</CODE><SPAN>&nbsp;</SPAN>โ€” replace<SPAN>&nbsp;</SPAN><CODE>confirmMove</CODE><SPAN>&nbsp;</SPAN>and add the<SPAN>&nbsp;</SPAN><CODE>TurnComplete</CODE><SPAN>&nbsp;</SPAN>subscriber:</P><BLOCKQUOTE dir="auto"><P class=""><STRONG>Before implementing<SPAN>&nbsp;</SPAN><CODE>confirmMove</CODE>:</STRONG><SPAN>&nbsp;</SPAN>Your Week 2<SPAN>&nbsp;</SPAN><CODE>rollDice</CODE><SPAN>&nbsp;</SPAN>handler must store two additional fields during the<SPAN>&nbsp;</SPAN><CODE>UPDATE(Players)</CODE><SPAN>&nbsp;</SPAN>call. If you haven't already, add these to the<SPAN>&nbsp;</SPAN><CODE>.set({...})</CODE><SPAN>&nbsp;</SPAN>block in<SPAN>&nbsp;</SPAN><CODE>rollDice</CODE>:</P><PRE><CODE><SPAN class="">prevPosition</SPAN>: player.<SPAN class="">position</SPAN>, <SPAN class="">// position BEFORE the move (used for TurnLog)</SPAN> <SPAN class="">lastEventType</SPAN>: event?.<SPAN class="">type</SPAN> ?? <SPAN class="">'normal'</SPAN> <SPAN class="">// ladder/snake/doubleSnake/normal</SPAN> </CODE></PRE><P class="">These are read by<SPAN>&nbsp;</SPAN><CODE>confirmMove</CODE><SPAN>&nbsp;</SPAN>when writing the<SPAN>&nbsp;</SPAN><CODE>TurnLog</CODE><SPAN>&nbsp;</SPAN>entry.</P></BLOCKQUOTE><PRE><CODE><SPAN class="">// โ”€โ”€ confirmMove โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€</SPAN> <SPAN class="">// After this returns, CAP sets status โ†’ #Waiting.</SPAN> <SPAN class="">// We then find the next player and set them to #Playing.</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'confirmMove'</SPAN>, <SPAN class="">'Players'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">// Players and TurnLog are already destructured at the top of init()</SPAN> <SPAN class="">const</SPAN> player = <SPAN class="">await</SPAN> <SPAN class="">SELECT</SPAN>.<SPAN class="">one</SPAN>.<SPAN class="">from</SPAN>(<SPAN class="">Players</SPAN>).<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: req.<SPAN class="">params</SPAN>[<SPAN class="">0</SPAN>].<SPAN class="">ID</SPAN> }) <SPAN class="">if</SPAN> (!player) <SPAN class="">return</SPAN> req.<SPAN class="">error</SPAN>(<SPAN class="">404</SPAN>, <SPAN class="">'Player not found'</SPAN>) <SPAN class="">// โ”€โ”€ Check win condition before handing off โ”€โ”€</SPAN> <SPAN class="">if</SPAN> (player.<SPAN class="">position</SPAN> === <SPAN class="">100</SPAN>) { <SPAN class="">// Delegate to winGame โ€” correct CAP bound-action call syntax</SPAN> <SPAN class="">await</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">send</SPAN>({ <SPAN class="">event</SPAN>: <SPAN class="">'winGame'</SPAN>, <SPAN class="">entity</SPAN>: <SPAN class="">'Players'</SPAN>, <SPAN class="">params</SPAN>: [{ <SPAN class="">ID</SPAN>: player.<SPAN class="">ID</SPAN> }] }) <SPAN class="">return</SPAN> } <SPAN class="">// โ”€โ”€ Log this turn โ”€โ”€</SPAN> <SPAN class="">const</SPAN> [{ <SPAN class="">n</SPAN>: count }] = <SPAN class="">await</SPAN> <SPAN class="">SELECT</SPAN><SPAN class="">`count(*) as n`</SPAN>.<SPAN class="">from</SPAN>(<SPAN class="">TurnLog</SPAN>) .<SPAN class="">where</SPAN>({ <SPAN class="">session_ID</SPAN>: player.<SPAN class="">session_ID</SPAN> }) <SPAN class="">const</SPAN> turnNumber = (count || <SPAN class="">0</SPAN>) + <SPAN class="">1</SPAN> <SPAN class="">await</SPAN> <SPAN class="">INSERT</SPAN>.<SPAN class="">into</SPAN>(<SPAN class="">TurnLog</SPAN>).<SPAN class="">entries</SPAN>({ <SPAN class="">ID</SPAN>: cds.<SPAN class="">utils</SPAN>.<SPAN class="">uuid</SPAN>(), <SPAN class="">session_ID</SPAN>: player.<SPAN class="">session_ID</SPAN>, <SPAN class="">player_ID</SPAN>: player.<SPAN class="">ID</SPAN>, turnNumber, <SPAN class="">diceRoll</SPAN>: player.<SPAN class="">lastRoll</SPAN>, <SPAN class="">fromSquare</SPAN>: player.<SPAN class="">prevPosition</SPAN>, <SPAN class="">// pre-roll position stored by rollDice</SPAN> <SPAN class="">toSquare</SPAN>: player.<SPAN class="">position</SPAN>, <SPAN class="">eventType</SPAN>: player.<SPAN class="">lastEventType</SPAN> ?? <SPAN class="">'normal'</SPAN>, <SPAN class="">// event type stored by rollDice</SPAN> <SPAN class="">timestamp</SPAN>: <SPAN class="">new</SPAN> <SPAN class="">Date</SPAN>().<SPAN class="">toISOString</SPAN>() }) <SPAN class="">// โ”€โ”€ Find next player in turn order โ”€โ”€</SPAN> <SPAN class="">const</SPAN> players = <SPAN class="">await</SPAN> <SPAN class="">SELECT</SPAN>.<SPAN class="">from</SPAN>(<SPAN class="">Players</SPAN>) .<SPAN class="">where</SPAN>({ <SPAN class="">session_ID</SPAN>: player.<SPAN class="">session_ID</SPAN> }) .<SPAN class="">orderBy</SPAN>(<SPAN class="">'turnOrder'</SPAN>) <SPAN class="">const</SPAN> idx = players.<SPAN class="">findIndex</SPAN>(<SPAN class=""><SPAN class="">p</SPAN> =&gt;</SPAN> p.<SPAN class="">ID</SPAN> === player.<SPAN class="">ID</SPAN>) <SPAN class="">// Skip finished players โ€” guard counter prevents infinite loop if all finish simultaneously</SPAN> <SPAN class="">let</SPAN> nextIdx = (idx + <SPAN class="">1</SPAN>) % players.<SPAN class="">length</SPAN> <SPAN class="">let</SPAN> guard = <SPAN class="">0</SPAN> <SPAN class="">while</SPAN> (players[nextIdx].<SPAN class="">TurnStatus</SPAN> === <SPAN class="">'Finished'</SPAN> &amp;&amp; guard++ &lt; players.<SPAN class="">length</SPAN>) { nextIdx = (nextIdx + <SPAN class="">1</SPAN>) % players.<SPAN class="">length</SPAN> } <SPAN class="">const</SPAN> next = players[nextIdx] <SPAN class="">await</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">emit</SPAN>(<SPAN class="">'TurnComplete'</SPAN>, { <SPAN class="">sessionID</SPAN>: player.<SPAN class="">session_ID</SPAN>, <SPAN class="">playerID</SPAN>: player.<SPAN class="">ID</SPAN>, <SPAN class="">nextPlayerID</SPAN>: next.<SPAN class="">ID</SPAN>, turnNumber }) <SPAN class="">// Activate next player โ€” use direct UPDATE, not the flow action,</SPAN> <SPAN class="">// because we're already inside a handler</SPAN> <SPAN class="">await</SPAN> <SPAN class="">UPDATE</SPAN>(<SPAN class="">Players</SPAN>) .<SPAN class="">set</SPAN>({ <SPAN class="">TurnStatus</SPAN>: <SPAN class="">'Playing'</SPAN> }) .<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: next.<SPAN class="">ID</SPAN> }) }) <SPAN class="">// โ”€โ”€ TurnComplete event subscriber โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€</SPAN> <SPAN class="">// <SPAN class="">NOTE:</SPAN> this.after() on custom events receives (handlerResult, req) โ€” the</SPAN> <SPAN class="">// first argument is undefined (no return value from the event). Use req.data.</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">after</SPAN>(<SPAN class="">'TurnComplete'</SPAN>, <SPAN class="">async</SPAN> (_results, req) =&gt; { <SPAN class="">const</SPAN> data = req.<SPAN class="">data</SPAN> <SPAN class="">console</SPAN>.<SPAN class="">log</SPAN>(<SPAN class="">`[TurnComplete] session=<SPAN class="">${data.sessionID}</SPAN> nextPlayer=<SPAN class="">${data.nextPlayerID}</SPAN> turn=<SPAN class="">${data.turnNumber}</SPAN>`</SPAN>) })</CODE></PRE><H3 id="3-add-a-session-level-startgame-action" id="toc-hId-377034616">3. Add a session-level<SPAN>&nbsp;</SPAN><CODE>startGame</CODE><SPAN>&nbsp;</SPAN>action</H3><P class="">Players need a way to transition a session from "lobby" to "in progress". Add to<SPAN>&nbsp;</SPAN><CODE>db/schema.cds</CODE>:</P><PRE><CODE>type SessionStatus : String enum { Lobby; // Accepting players, not yet started InProgress; // Game is running Complete; // A player has won } @flow.status: SessionStatus entity GameSessions { key ID : UUID; name : String(100); @cds.on.insert: $now createdAt : Timestamp; startedAt : Timestamp; finishedAt : Timestamp; winner : Association to Players; @readonly SessionStatus : SessionStatus default #Lobby; }</CODE></PRE><P class="">Add to<SPAN>&nbsp;</SPAN><CODE>srv/game-service.cds</CODE><SPAN>&nbsp;</SPAN>actions on<SPAN>&nbsp;</SPAN><CODE>GameSessions</CODE>:</P><PRE><CODE>@flow.status: SessionStatus entity GameSessions as projection on snakeladder.GameSessions actions { @from: [ #Lobby ] @to: #InProgress action startGame(); @from: [ #InProgress ] @to: #Complete action endGame(); }</CODE></PRE><BLOCKQUOTE dir="auto"><P class=""><STRONG>Also verify<SPAN>&nbsp;</SPAN><CODE>startTurn</CODE><SPAN>&nbsp;</SPAN>is still in your<SPAN>&nbsp;</SPAN><CODE>Players</CODE><SPAN>&nbsp;</SPAN>actions</STRONG><SPAN>&nbsp;</SPAN>from Week 2. If you rebuilt your service file, make sure it includes:</P><PRE><CODE>@from: [ #Waiting ] @to: #Playing action startTurn();</CODE></PRE><P class="">In the multiplayer flow,<SPAN>&nbsp;</SPAN><CODE>startGame</CODE><SPAN>&nbsp;</SPAN>sets the first player directly to<SPAN>&nbsp;</SPAN><CODE>Playing</CODE>, and<SPAN>&nbsp;</SPAN><CODE>confirmMove</CODE><SPAN>&nbsp;</SPAN>advances turns via direct<SPAN>&nbsp;</SPAN><CODE>UPDATE</CODE><SPAN>&nbsp;</SPAN>โ€” so<SPAN>&nbsp;</SPAN><CODE>startTurn</CODE><SPAN>&nbsp;</SPAN>is not strictly required for multiplayer turn rotation. However, it's still useful for manual testing and for single-player Week 2 scenarios. Keep it in your service definition.</P></BLOCKQUOTE><P class="">Implement<SPAN>&nbsp;</SPAN><CODE>startGame</CODE><SPAN>&nbsp;</SPAN>in<SPAN>&nbsp;</SPAN><CODE>game-service.js</CODE>:</P><PRE><CODE><SPAN class="">// Players, BoardSquares, TurnLog are already destructured at the top of init()</SPAN> <SPAN class="">// Make sure your top-of-init destructure includes ALL three:</SPAN> <SPAN class="">// const { Players, BoardSquares, TurnLog } = this.entities</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'startGame'</SPAN>, <SPAN class="">'GameSessions'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">const</SPAN> sessionID = req.<SPAN class="">params</SPAN>[<SPAN class="">0</SPAN>].<SPAN class="">ID</SPAN> <SPAN class="">// All players start in Waiting โ€” set the first in turn order to Playing</SPAN> <SPAN class="">const</SPAN> players = <SPAN class="">await</SPAN> <SPAN class="">SELECT</SPAN>.<SPAN class="">from</SPAN>(<SPAN class="">Players</SPAN>) .<SPAN class="">where</SPAN>({ <SPAN class="">session_ID</SPAN>: sessionID }) .<SPAN class="">orderBy</SPAN>(<SPAN class="">'turnOrder'</SPAN>) <SPAN class="">if</SPAN> (players.<SPAN class="">length</SPAN> &lt; <SPAN class="">2</SPAN>) { <SPAN class="">return</SPAN> req.<SPAN class="">reject</SPAN>(<SPAN class="">400</SPAN>, <SPAN class="">'A game needs at least 2 players to start.'</SPAN>) } <SPAN class="">await</SPAN> <SPAN class="">UPDATE</SPAN>(<SPAN class="">Players</SPAN>) .<SPAN class="">set</SPAN>({ <SPAN class="">TurnStatus</SPAN>: <SPAN class="">'Playing'</SPAN> }) .<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: players[<SPAN class="">0</SPAN>].<SPAN class="">ID</SPAN> }) <SPAN class="">await</SPAN> <SPAN class="">UPDATE</SPAN>(<SPAN class="">'snakeladder.GameSessions'</SPAN>) .<SPAN class="">set</SPAN>({ <SPAN class="">startedAt</SPAN>: <SPAN class="">new</SPAN> <SPAN class="">Date</SPAN>().<SPAN class="">toISOString</SPAN>() }) .<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: sessionID }) })</CODE></PRE><P class="">Also add the<SPAN>&nbsp;</SPAN><CODE>endGame</CODE><SPAN>&nbsp;</SPAN>handler. This action is triggered by the<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/39488">@flow</a>.status</CODE><SPAN>&nbsp;</SPAN>machinery (it transitions<SPAN>&nbsp;</SPAN><CODE>SessionStatus</CODE><SPAN>&nbsp;</SPAN>to<SPAN>&nbsp;</SPAN><CODE>#Complete</CODE><SPAN>&nbsp;</SPAN>automatically), but it must also set<SPAN>&nbsp;</SPAN><CODE>finishedAt</CODE>:</P><PRE><CODE><SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'endGame'</SPAN>, <SPAN class="">'GameSessions'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">await</SPAN> <SPAN class="">UPDATE</SPAN>(<SPAN class="">'snakeladder.GameSessions'</SPAN>) .<SPAN class="">set</SPAN>({ <SPAN class="">finishedAt</SPAN>: <SPAN class="">new</SPAN> <SPAN class="">Date</SPAN>().<SPAN class="">toISOString</SPAN>() }) .<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: req.<SPAN class="">params</SPAN>[<SPAN class="">0</SPAN>].<SPAN class="">ID</SPAN> }) })</CODE></PRE><H3 id="4-test-multiplayer-turn-rotation" id="toc-hId-180521111">4. Test multiplayer turn rotation</H3><P class="">Create a session with two players and start the game:</P><BLOCKQUOTE dir="auto"><P class=""><STRONG>Tip:</STRONG><SPAN>&nbsp;</SPAN>Omit the<SPAN>&nbsp;</SPAN><CODE>ID</CODE><SPAN>&nbsp;</SPAN>field in POST bodies โ€” CAP auto-generates a UUID. Copy the returned ID from each response to use in subsequent requests.</P></BLOCKQUOTE><PRE><CODE>POST http://localhost:4004/odata/v4/game/GameSessions <SPAN class="">Content-Type</SPAN><SPAN class="">: </SPAN>application/json { "name": "My First Multiplayer Game" }</CODE></PRE><PRE><CODE>POST http://localhost:4004/odata/v4/game/Players <SPAN class="">Content-Type</SPAN><SPAN class="">: </SPAN>application/json { "session_ID": "&lt;session-ID-from-above&gt;", "name": "Alice", "turnOrder": 1 }</CODE></PRE><PRE><CODE>POST http://localhost:4004/odata/v4/game/Players <SPAN class="">Content-Type</SPAN><SPAN class="">: </SPAN>application/json { "session_ID": "&lt;session-ID-from-above&gt;", "name": "Bob", "turnOrder": 2 }</CODE></PRE><PRE><CODE>POST http://localhost:4004/odata/v4/game/GameSessions(&lt;session-ID&gt;)/startGame <SPAN class="">Content-Type</SPAN><SPAN class="">: </SPAN>application/json {}</CODE></PRE><P class="">Now Alice should be<SPAN>&nbsp;</SPAN><CODE>#Playing</CODE><SPAN>&nbsp;</SPAN>and Bob should be<SPAN>&nbsp;</SPAN><CODE>#Waiting</CODE>. Roll for Alice, confirm her move, and Bob should automatically become<SPAN>&nbsp;</SPAN><CODE>#Playing</CODE>.</P><BLOCKQUOTE dir="auto"><P class=""><STRONG>Note on service path:</STRONG><SPAN>&nbsp;</SPAN>CAP maps<SPAN>&nbsp;</SPAN><CODE>GameService</CODE><SPAN>&nbsp;</SPAN>to<SPAN>&nbsp;</SPAN><CODE>/odata/v4/game</CODE>. Replace<SPAN>&nbsp;</SPAN><CODE>GameService</CODE><SPAN>&nbsp;</SPAN>in all URLs with<SPAN>&nbsp;</SPAN><CODE>game</CODE>. See the Week 1 note for details.</P></BLOCKQUOTE><P class="">Verify by checking both players' statuses after<SPAN>&nbsp;</SPAN><CODE>confirmMove</CODE>:</P><PRE><CODE>GET http://localhost:4004/odata/v4/game/Players?$filter=session_ID eq '&lt;session-ID&gt;'</CODE></PRE><H3 id="5-connect-the-cap-mcp-server" id="toc-hId--91223763">5. Connect the CAP MCP Server</H3><P class="">The<SPAN>&nbsp;</SPAN><STRONG>CAP MCP server</STRONG><SPAN>&nbsp;</SPAN>exposes your<SPAN>&nbsp;</SPAN><CODE>GameService</CODE><SPAN>&nbsp;</SPAN>as tools that any MCP-compatible AI assistant (Claude Code, Claude Desktop, Opencode) can call. The AI can query board state, roll dice, narrate turns โ€” all via natural language.</P><H4 id="install" id="toc-hId--581140275">Install</H4><PRE><CODE>npm add @cap-js/mcp</CODE></PRE><P class="">That's it. No extra configuration needed.<SPAN>&nbsp;</SPAN><CODE>@cap-js/mcp</CODE><SPAN>&nbsp;</SPAN>auto-detects CAP services during<SPAN>&nbsp;</SPAN><CODE>cds watch</CODE>.</P><H4 id="annotate-gameservice" id="toc-hId--777653780">Annotate GameService</H4><P class="">Add<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/1914304">@McP</a></CODE><SPAN>&nbsp;</SPAN>to your service declaration in<SPAN>&nbsp;</SPAN><CODE>srv/game-service.cds</CODE>, plus<SPAN>&nbsp;</SPAN><CODE>@title</CODE><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><CODE>@description</CODE><SPAN>&nbsp;</SPAN>on entities and actions so the AI understands the game domain:</P><BLOCKQUOTE dir="auto"><P class=""><STRONG>This shows only the annotations to add โ€” keep your existing action definitions unchanged.</STRONG><SPAN>&nbsp;</SPAN>The<SPAN>&nbsp;</SPAN><CODE>// ... other actions</CODE><SPAN>&nbsp;</SPAN>placeholder represents your existing<SPAN>&nbsp;</SPAN><CODE>rollDice</CODE>,<SPAN>&nbsp;</SPAN><CODE>confirmMove</CODE>, etc.</P></BLOCKQUOTE><PRE><CODE>/** * Snake &amp; Ladder Game Service โ€” multiplayer board game engine. * Players take turns rolling dice and moving tokens across a 10ร—10 board. * Snakes send players back; ladders advance them. First to reach square 100 wins. */ @mcp @protocol: ['odata-v4', 'mcp'] // keep OData alongside MCP โ€” REQUIRED or OData drops @title: 'Snake &amp; Ladder Game' service GameService { @title: 'Game Sessions' @description: 'Create a session, add players, then call startGame. SessionStatus: Lobby โ†’ InProgress โ†’ Complete.' @flow.status: SessionStatus entity GameSessions as projection on snakeladder.GameSessions actions { /** Start the game. Requires at least 2 players. Sets first player (turnOrder=1) to Playing. */ @from: [ #Lobby ] @to: #InProgress action startGame(); // ... other actions } @title: 'Players' @description: 'Each player has a position (1-100) and TurnStatus. Call rollDice when Playing, then confirmMove to end the turn.' @flow.status: TurnStatus @cds.redirection.target entity Players as projection on snakeladder.Players actions { /** * Roll a 6-sided dice and move the player. * Returns: roll (1-6), position (new square after snakes/ladders), event (normal/ladder/snake/doubleSnake). */ @from: [ #Playing ] @to: #Moving action rollDice() returns { roll: Integer; position: Integer; event: String; }; // ... other actions } @title: 'Board State' @description: 'Live view of all player positions. Filter by session_ID to see a specific game.' @readonly entity BoardState as select from snakeladder.Players { key ID, name, position, TurnStatus, lastRoll, session.ID as session_ID : UUID, // typed alias โ€” makes session_ID OData-filterable session.name as sessionName }; }</CODE></PRE><H4 id="start-cds-watch-and-the-mcp-endpoint-appears-automatically" id="toc-hId--974167285">Start<SPAN>&nbsp;</SPAN><CODE>cds watch</CODE><SPAN>&nbsp;</SPAN>and the MCP endpoint appears automatically</H4><PRE><CODE>cds watch</CODE></PRE><P class="">Look for this line in the output:</P><PRE><CODE>[cds] - serving GameService { at: [ '/odata/v4/game', '/mcp/game' ], ... }</CODE></PRE><P class="">The MCP server is live at<SPAN>&nbsp;</SPAN><STRONG><CODE><A href="http://localhost:4004/mcp/game" target="_blank" rel="noopener nofollow noreferrer">http://localhost:4004/mcp/game</A></CODE></STRONG>.</P><H4 id="add-mcp-callable-wrapper-functions" id="toc-hId--1170680790">Add MCP-callable wrapper functions</H4><P class="">The<SPAN>&nbsp;</SPAN><CODE>@cap-js/mcp</CODE><SPAN>&nbsp;</SPAN><CODE>query</CODE><SPAN>&nbsp;</SPAN>tool reads data; bound actions on entities (like<SPAN>&nbsp;</SPAN><CODE>rollDice</CODE>) aren't directly callable by the AI. Add unbound service functions that the AI can call via<SPAN>&nbsp;</SPAN><CODE>per_action_tool</CODE>. Add these to<SPAN>&nbsp;</SPAN><CODE>srv/game-service.cds</CODE><SPAN>&nbsp;</SPAN>inside the service block:</P><PRE><CODE>// โ”€โ”€ MCP-callable unbound functions โ€” allow AI to drive the game โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ /** Start a game session. Requires sessionID of a Lobby session with โ‰ฅ2 players. */ function mcpStartGame(sessionID: UUID) returns String; /** Roll the dice for the current Playing player. */ function mcpRollDice(playerID: UUID) returns { roll: Integer; position: Integer; event: String; }; /** Confirm a move and rotate the turn to the next player. */ function mcpConfirmMove(playerID: UUID) returns String; /** Get the current board state for a game session. */ function mcpBoardState(sessionID: UUID) returns array of { name: String; position: Integer; TurnStatus: String; lastRoll: Integer; }; /** All 100 squares with snake/ladder info. */ function mcpBoardMap() returns array of { square: Integer; snakeTo: Integer; ladderTo: Integer; isDoubleHead: Boolean; };</CODE></PRE><P class="">Implement them in<SPAN>&nbsp;</SPAN><CODE>srv/game-service.js</CODE><SPAN>&nbsp;</SPAN>(add before<SPAN>&nbsp;</SPAN><CODE>return super.init()</CODE><span class="lia-unicode-emoji" title=":disappointed_face:">๐Ÿ˜ž</span></P><BLOCKQUOTE dir="auto"><P class=""><STRONG>Critical: use the object form of<SPAN>&nbsp;</SPAN><CODE>this.send()</CODE><SPAN>&nbsp;</SPAN>for bound entity actions.</STRONG><SPAN>&nbsp;</SPAN>The string form<SPAN>&nbsp;</SPAN><CODE>this.send('rollDice', { params })</CODE><SPAN>&nbsp;</SPAN>puts<SPAN>&nbsp;</SPAN><CODE>{ params }</CODE><SPAN>&nbsp;</SPAN>into<SPAN>&nbsp;</SPAN><CODE>req.data</CODE><SPAN>&nbsp;</SPAN>of the dispatched request โ€” bound action handlers read the entity key from<SPAN>&nbsp;</SPAN><CODE>req.params[0].ID</CODE>, which will be<SPAN>&nbsp;</SPAN><CODE>undefined</CODE>. Your handlers will run silently returning success but touching nothing. Always use the object form:</P><PRE><CODE><SPAN class="">// โœ— WRONG โ€” params land in req.data, req.params is empty</SPAN> <SPAN class="">await</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">send</SPAN>(<SPAN class="">'rollDice'</SPAN>, { <SPAN class="">params</SPAN>: [{ <SPAN class="">ID</SPAN>: playerID }] }) <SPAN class="">// โœ“ CORRECT โ€” params routed to req.params[0]</SPAN> <SPAN class="">await</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">send</SPAN>({ <SPAN class="">event</SPAN>: <SPAN class="">'rollDice'</SPAN>, <SPAN class="">entity</SPAN>: <SPAN class="">'Players'</SPAN>, <SPAN class="">params</SPAN>: [{ <SPAN class="">ID</SPAN>: playerID }] })</CODE></PRE></BLOCKQUOTE><PRE><CODE><SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'mcpStartGame'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">// Use object form: string form puts params in req.data, not req.params</SPAN> <SPAN class="">await</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">send</SPAN>({ <SPAN class="">event</SPAN>: <SPAN class="">'startGame'</SPAN>, <SPAN class="">entity</SPAN>: <SPAN class="">'GameSessions'</SPAN>, <SPAN class="">params</SPAN>: [{ <SPAN class="">ID</SPAN>: req.<SPAN class="">data</SPAN>.<SPAN class="">sessionID</SPAN> }] }) <SPAN class="">return</SPAN> <SPAN class="">`Game started for session <SPAN class="">${req.data.sessionID}</SPAN>`</SPAN> }) <SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'mcpRollDice'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">return</SPAN> <SPAN class="">await</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">send</SPAN>({ <SPAN class="">event</SPAN>: <SPAN class="">'rollDice'</SPAN>, <SPAN class="">entity</SPAN>: <SPAN class="">'Players'</SPAN>, <SPAN class="">params</SPAN>: [{ <SPAN class="">ID</SPAN>: req.<SPAN class="">data</SPAN>.<SPAN class="">playerID</SPAN> }] }) }) <SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'mcpConfirmMove'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">await</SPAN> <SPAN class="">this</SPAN>.<SPAN class="">send</SPAN>({ <SPAN class="">event</SPAN>: <SPAN class="">'confirmMove'</SPAN>, <SPAN class="">entity</SPAN>: <SPAN class="">'Players'</SPAN>, <SPAN class="">params</SPAN>: [{ <SPAN class="">ID</SPAN>: req.<SPAN class="">data</SPAN>.<SPAN class="">playerID</SPAN> }] }) <SPAN class="">return</SPAN> <SPAN class="">`Move confirmed for player <SPAN class="">${req.data.playerID}</SPAN>`</SPAN> }) <SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'mcpBoardState'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">return</SPAN> <SPAN class="">await</SPAN> <SPAN class="">SELECT</SPAN>.<SPAN class="">from</SPAN>(<SPAN class="">Players</SPAN>).<SPAN class="">where</SPAN>({ <SPAN class="">session_ID</SPAN>: req.<SPAN class="">data</SPAN>.<SPAN class="">sessionID</SPAN> }) .<SPAN class="">columns</SPAN>(<SPAN class="">'name'</SPAN>,<SPAN class="">'position'</SPAN>,<SPAN class="">'TurnStatus'</SPAN>,<SPAN class="">'lastRoll'</SPAN>).<SPAN class="">orderBy</SPAN>(<SPAN class="">'turnOrder'</SPAN>) }) <SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'mcpBoardMap'</SPAN>, <SPAN class="">async</SPAN> () =&gt; <SPAN class="">SELECT</SPAN>.<SPAN class="">from</SPAN>(<SPAN class="">BoardSquares</SPAN>).<SPAN class="">orderBy</SPAN>(<SPAN class="">'square'</SPAN>))</CODE></PRE><P class="">Enable<SPAN>&nbsp;</SPAN><CODE>per_action_tool</CODE><SPAN>&nbsp;</SPAN>in<SPAN>&nbsp;</SPAN><CODE>package.json</CODE>:</P><PRE><CODE><SPAN class="">{</SPAN> <SPAN class="">"cds"</SPAN><SPAN class="">:</SPAN> <SPAN class="">{</SPAN> <SPAN class="">"mcp"</SPAN><SPAN class="">:</SPAN> <SPAN class="">{</SPAN> <SPAN class="">"per_action_tool"</SPAN><SPAN class="">:</SPAN> <SPAN class=""><SPAN class="">true</SPAN></SPAN> <SPAN class="">}</SPAN> <SPAN class="">}</SPAN> <SPAN class="">}</SPAN> </CODE></PRE><P class="">After restarting, the MCP server exposes<SPAN>&nbsp;</SPAN><STRONG>7 tools</STRONG>:<SPAN>&nbsp;</SPAN><CODE>describe</CODE>,<SPAN>&nbsp;</SPAN><CODE>query</CODE>,<SPAN>&nbsp;</SPAN><CODE>mcpStartGame</CODE>,<SPAN>&nbsp;</SPAN><CODE>mcpRollDice</CODE>,<SPAN>&nbsp;</SPAN><CODE>mcpConfirmMove</CODE>,<SPAN>&nbsp;</SPAN><CODE>mcpBoardState</CODE>,<SPAN>&nbsp;</SPAN><CODE>mcpBoardMap</CODE>.</P><H4 id="toc-hId--1367194295">&nbsp;</H4><H4 id="toc-hId--1563707800">Note: <EM><STRONG>You can use any MCP tool of your choice.&nbsp;</STRONG></EM></H4><H4 id="use-with-claude-code-auto-wired--just-ask" id="toc-hId--1760221305">Use with Claude Code (auto-wired โ€” just ask)</H4><P class="">With<SPAN>&nbsp;</SPAN><CODE>cds watch</CODE><SPAN>&nbsp;</SPAN>running, open a new terminal and ask Claude Code directly:</P><PRE><CODE>claude <SPAN class="">"What is the current board state? Who is in the lead?"</SPAN> </CODE></PRE><PRE><CODE>claude <SPAN class="">"Show me the turn history for the most recent game session."</SPAN> </CODE></PRE><PRE><CODE>claude <SPAN class="">"Which squares have snakes and which have ladders? Summarise the dangerous zones."</SPAN> </CODE></PRE><P class="">Claude Code uses the<SPAN>&nbsp;</SPAN><CODE>query</CODE><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><CODE>describe</CODE><SPAN>&nbsp;</SPAN>MCP tools to read your live game data and answer.</P><H4 id="use-with-claude-desktop-manual-wiring" id="toc-hId--1956734810">Use with Claude Desktop (manual wiring)</H4><P class="">If you want the AI to also roll dice and make moves, connect Claude Desktop. Add to<SPAN>&nbsp;</SPAN><CODE>claude_desktop_config.json</CODE><SPAN>&nbsp;</SPAN>(macOS:<SPAN>&nbsp;</SPAN><CODE>~/Library/Application Support/Claude/claude_desktop_config.json</CODE><span class="lia-unicode-emoji" title=":disappointed_face:">๐Ÿ˜ž</span></P><PRE><CODE><SPAN class="">{</SPAN> <SPAN class="">"mcpServers"</SPAN><SPAN class="">:</SPAN> <SPAN class="">{</SPAN> <SPAN class="">"snake-ladder"</SPAN><SPAN class="">:</SPAN> <SPAN class="">{</SPAN> <SPAN class="">"command"</SPAN><SPAN class="">:</SPAN> <SPAN class="">"npx"</SPAN><SPAN class="">,</SPAN> <SPAN class="">"args"</SPAN><SPAN class="">:</SPAN> <SPAN class="">[</SPAN><SPAN class="">"-y"</SPAN><SPAN class="">,</SPAN> <SPAN class="">"mcp-remote"</SPAN><SPAN class="">,</SPAN> <SPAN class="">"http://localhost:4004/mcp/game"</SPAN><SPAN class="">]</SPAN> <SPAN class="">}</SPAN> <SPAN class="">}</SPAN> <SPAN class="">}</SPAN> </CODE></PRE><P class="">Restart Claude Desktop, then try:</P><BLOCKQUOTE dir="auto"><P class=""><EM>"What is the current board state for the active game? Who is winning?"</EM></P></BLOCKQUOTE><BLOCKQUOTE dir="auto"><P class=""><EM>"Roll the dice for the current Playing player and tell me what happened."</EM></P></BLOCKQUOTE><BLOCKQUOTE dir="auto"><P class=""><EM>"Narrate the last 5 turns like a sports commentator."</EM></P></BLOCKQUOTE><H4 id="verify-the-mcp-endpoint--tools-list-and-action-tests" id="toc-hId-2141718981">Verify the MCP endpoint โ€” tools list and action tests</H4><P class=""><STRONG>Step 1 โ€” List all tools (should be exactly 7):</STRONG></P><PRE><CODE>curl -X POST http://localhost:4004/mcp/game \ -H <SPAN class="">"Content-Type: application/json"</SPAN> \ -H <SPAN class="">"Accept: application/json, text/event-stream"</SPAN> \ -d <SPAN class="">'{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'</SPAN> </CODE></PRE><P class="">Expected: SSE response with<SPAN>&nbsp;</SPAN><CODE>tools</CODE><SPAN>&nbsp;</SPAN>array containing<SPAN>&nbsp;</SPAN><CODE>query</CODE>,<SPAN>&nbsp;</SPAN><CODE>describe</CODE>,<SPAN>&nbsp;</SPAN><CODE>mcpStartGame</CODE>,<SPAN>&nbsp;</SPAN><CODE>mcpRollDice</CODE>,<SPAN>&nbsp;</SPAN><CODE>mcpConfirmMove</CODE>,<SPAN>&nbsp;</SPAN><CODE>mcpBoardState</CODE>,<SPAN>&nbsp;</SPAN><CODE>mcpBoardMap</CODE>.</P><P class=""><STRONG>Step 2 โ€” mcpBoardMap (no session needed):</STRONG></P><PRE><CODE>curl -X POST http://localhost:4004/mcp/game \ -H <SPAN class="">"Content-Type: application/json"</SPAN> \ -H <SPAN class="">"Accept: application/json, text/event-stream"</SPAN> \ -d <SPAN class="">'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"mcpBoardMap","arguments":{}}}'</SPAN> </CODE></PRE><P class="">Expected: 100 squares with<SPAN>&nbsp;</SPAN><CODE>snakeTo</CODE>,<SPAN>&nbsp;</SPAN><CODE>ladderTo</CODE>,<SPAN>&nbsp;</SPAN><CODE>isDoubleHead</CODE><SPAN>&nbsp;</SPAN>fields.</P><P class=""><STRONG>Step 3 โ€” mcpStartGame (use an existing Lobby session ID):</STRONG></P><PRE><CODE>curl -X POST http://localhost:4004/mcp/game \ -H <SPAN class="">"Content-Type: application/json"</SPAN> \ -H <SPAN class="">"Accept: application/json, text/event-stream"</SPAN> \ -d <SPAN class="">'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"mcpStartGame","arguments":{"sessionID":"&lt;your-session-id&gt;"}}}'</SPAN> </CODE></PRE><P class="">Expected response:<SPAN>&nbsp;</SPAN><CODE>"result": "Game started for session ..."</CODE>. Then verify with OData:</P><PRE><CODE>GET http://localhost:4004/odata/v4/game/GameSessions(&lt;session-id&gt;)?$select=SessionStatus,startedAt</CODE></PRE><P class=""><CODE>SessionStatus</CODE><SPAN>&nbsp;</SPAN>should be<SPAN>&nbsp;</SPAN><CODE>"InProgress"</CODE><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><CODE>startedAt</CODE><SPAN>&nbsp;</SPAN>not null.</P><BLOCKQUOTE dir="auto"><P class=""><STRONG>If<SPAN>&nbsp;</SPAN><CODE>mcpStartGame</CODE><SPAN>&nbsp;</SPAN>returns success but SessionStatus stays Lobby</STRONG>, you have the<SPAN>&nbsp;</SPAN><CODE>this.send()</CODE><SPAN>&nbsp;</SPAN>bug โ€” the string form was used instead of the object form. See the critical note above.</P></BLOCKQUOTE><P class=""><STRONG>Step 4 โ€” mcpRollDice (use the Playing player's ID):</STRONG></P><PRE><CODE>curl -X POST http://localhost:4004/mcp/game \ -H <SPAN class="">"Content-Type: application/json"</SPAN> \ -H <SPAN class="">"Accept: application/json, text/event-stream"</SPAN> \ -d <SPAN class="">'{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"mcpRollDice","arguments":{"playerID":"&lt;alice-id&gt;"}}}'</SPAN> </CODE></PRE><P class="">Expected:<SPAN>&nbsp;</SPAN><CODE>{"roll":N, "position":M, "event":"normal|ladder|snake|doubleSnake"}</CODE>. If you get<SPAN>&nbsp;</SPAN><CODE>"result": null</CODE>, the<SPAN>&nbsp;</SPAN><CODE>this.send()</CODE><SPAN>&nbsp;</SPAN>object form is not being used.</P><P class=""><STRONG>Step 5 โ€” mcpConfirmMove:</STRONG></P><PRE><CODE>curl -X POST http://localhost:4004/mcp/game \ -H <SPAN class="">"Content-Type: application/json"</SPAN> \ -H <SPAN class="">"Accept: application/json, text/event-stream"</SPAN> \ -d <SPAN class="">'{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"mcpConfirmMove","arguments":{"playerID":"&lt;alice-id&gt;"}}}'</SPAN> </CODE></PRE><P class="">Expected:<SPAN>&nbsp;</SPAN><CODE>"result": "Move confirmed for player ..."</CODE>. Check Bob's<SPAN>&nbsp;</SPAN><CODE>TurnStatus</CODE><SPAN>&nbsp;</SPAN>is now<SPAN>&nbsp;</SPAN><CODE>"Playing"</CODE>.</P><P class=""><STRONG>Step 6 โ€” mcpBoardState:</STRONG></P><PRE><CODE>curl -X POST http://localhost:4004/mcp/game \ -H <SPAN class="">"Content-Type: application/json"</SPAN> \ -H <SPAN class="">"Accept: application/json, text/event-stream"</SPAN> \ -d <SPAN class="">'{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"mcpBoardState","arguments":{"sessionID":"&lt;your-session-id&gt;"}}}'</SPAN> </CODE></PRE><P class="">Expected: array of players with<SPAN>&nbsp;</SPAN><CODE>name</CODE>,<SPAN>&nbsp;</SPAN><CODE>position</CODE>,<SPAN>&nbsp;</SPAN><CODE>TurnStatus</CODE>,<SPAN>&nbsp;</SPAN><CODE>lastRoll</CODE>.</P><BLOCKQUOTE dir="auto"><P class=""><STRONG>MCP uses SSE transport</STRONG><SPAN>&nbsp;</SPAN>โ€” responses arrive as<SPAN>&nbsp;</SPAN><CODE>event: message\ndata: {...}</CODE>. Strip the<SPAN>&nbsp;</SPAN><CODE>data:<SPAN>&nbsp;</SPAN></CODE>prefix to parse JSON. HTTP clients that don't support SSE (like some REST clients) will show the raw text.</P></BLOCKQUOTE><HR /><H2 id="understanding-the-two-state-machines" id="toc-hId--1594772115">Understanding the Two State Machines</H2><P class="">After this week you have<SPAN>&nbsp;</SPAN><STRONG>two independent status flows</STRONG><SPAN>&nbsp;</SPAN>running in your service:</P><P>Entity Field States Purpose</P><TABLE><TBODY><TR><TD><CODE>GameSessions</CODE></TD><TD><CODE>SessionStatus</CODE></TD><TD>Lobby โ†’ InProgress โ†’ Complete</TD><TD>Governs the game lifecycle</TD></TR><TR><TD><CODE>Players</CODE></TD><TD><CODE>TurnStatus</CODE></TD><TD>Waiting โ†’ Playing โ†’ Moving โ†’ Blocked / Finished</TD><TD>Governs each player's turn</TD></TR></TBODY></TABLE><P class="">The session state machine controls<SPAN>&nbsp;</SPAN><EM>whether the game is happening</EM>. The player state machine controls<SPAN>&nbsp;</SPAN><EM>whose turn it is and what they can do</EM>. CAP enforces both independently with their own<SPAN>&nbsp;</SPAN><CODE><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/39488">@flow</a>.status</CODE><SPAN>&nbsp;</SPAN>annotations.</P><HR /><H2 id="stretch-goal" id="toc-hId--1791285620">Stretch Goal</H2><P class="">Add a<SPAN>&nbsp;</SPAN><CODE>skipTurn</CODE><SPAN>&nbsp;</SPAN>action that a blocked player can call as an alternative to<SPAN>&nbsp;</SPAN><CODE>unblockPlayer</CODE>, which also records how many turns they've been blocked:</P><PRE><CODE>@from: [ #Blocked ] @to: $flow.previous action skipTurn();</CODE></PRE><PRE><CODE><SPAN class="">this</SPAN>.<SPAN class="">on</SPAN>(<SPAN class="">'skipTurn'</SPAN>, <SPAN class="">'Players'</SPAN>, <SPAN class="">async</SPAN> (req) =&gt; { <SPAN class="">const</SPAN> player = <SPAN class="">await</SPAN> <SPAN class="">SELECT</SPAN>.<SPAN class="">one</SPAN>.<SPAN class="">from</SPAN>(<SPAN class="">Players</SPAN>).<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: req.<SPAN class="">params</SPAN>[<SPAN class="">0</SPAN>].<SPAN class="">ID</SPAN> }) <SPAN class="">await</SPAN> <SPAN class="">UPDATE</SPAN>(<SPAN class="">Players</SPAN>) .<SPAN class="">set</SPAN>({ <SPAN class="">turnsBlocked</SPAN>: (player.<SPAN class="">turnsBlocked</SPAN> || <SPAN class="">0</SPAN>) + <SPAN class="">1</SPAN> }) .<SPAN class="">where</SPAN>({ <SPAN class="">ID</SPAN>: player.<SPAN class="">ID</SPAN> }) })</CODE></PRE><BLOCKQUOTE dir="auto"><P class=""><STRONG>Note:</STRONG><SPAN>&nbsp;</SPAN><CODE>BoardState</CODE><SPAN>&nbsp;</SPAN>is already included as a required entity in Step 5 (inside the<SPAN>&nbsp;</SPAN><CODE>@mcp</CODE>-annotated service). The<SPAN>&nbsp;</SPAN><CODE>@cds.on.insert: $now</CODE><SPAN>&nbsp;</SPAN>annotation on<SPAN>&nbsp;</SPAN><CODE>GameSessions.createdAt</CODE><SPAN>&nbsp;</SPAN>coexists correctly with the Week 1 seed CSV โ€” CAP uses the annotation for programmatic inserts but still honours explicit values in seed files.</P></BLOCKQUOTE><HR /><H2 id="how-to-submit" id="toc-hId--1987799125">How to Submit</H2><P class="">Reply to the<SPAN>&nbsp;</SPAN><STRONG>Week 3 blog post</STRONG><SPAN>&nbsp;</SPAN>on SAP Community with a comment containing:</P><OL class=""><LI>A screenshot or JSON response showing two players after<SPAN>&nbsp;</SPAN><CODE>confirmMove</CODE><SPAN>&nbsp;</SPAN>โ€” one<SPAN>&nbsp;</SPAN><CODE>Waiting</CODE>, one<SPAN>&nbsp;</SPAN><CODE>Playing</CODE></LI><LI>A screenshot or curl response showing<SPAN>&nbsp;</SPAN><CODE>mcpRollDice</CODE><SPAN>&nbsp;</SPAN>returning<SPAN>&nbsp;</SPAN><CODE>{roll, position, event}</CODE><SPAN>&nbsp;</SPAN>(not<SPAN>&nbsp;</SPAN><CODE>null</CODE>) โ€” proves the MCP action tools are wired correctly</LI><LI>A screenshot of<SPAN>&nbsp;</SPAN><STRONG>Claude Code</STRONG><SPAN>&nbsp;</SPAN>or<SPAN>&nbsp;</SPAN><STRONG>Claude Desktop</STRONG><SPAN><STRONG>&nbsp;( or any MCP tool client of your choice)</STRONG>&nbsp;</SPAN>querying your game state via MCP โ€” even a simple "who is winning?" response counts</LI><LI>Optional but celebrated: a short AI-narrated turn log using<SPAN>&nbsp;</SPAN><CODE>mcpBoardState</CODE><SPAN>&nbsp;</SPAN>+<SPAN>&nbsp;</SPAN><CODE>TurnLog</CODE></LI></OL><HR /><H2 id="whats-next" id="toc-hId-2110654666">What's Next</H2><P class="">In<SPAN>&nbsp;</SPAN><STRONG>Week 4 โ€” Fiori UI &amp; CAP MCP</STRONG>, you wire up the ready-made board UI, add your service URL to see your live game on a real Snake &amp; Ladder board to complete the challenge.</P><HR /><H2 id="resources" id="toc-hId-1914141161">Resources</H2><UL class=""><LI><A href="https://cap.cloud.sap/docs/guides/services/status-flows" target="_blank" rel="noopener nofollow noreferrer">CAP Status Flows Documentation</A></LI><LI><A href="https://cap.cloud.sap/docs/guides/protocols/mcp" target="_self" rel="nofollow noopener noreferrer">CAP MCP Server</A></LI><LI><A href="https://cap.cloud.sap/docs/node.js/core-services#srv-emit-event" target="_blank" rel="noopener nofollow noreferrer">CAP Node.js Events</A></LI></UL> 2026-07-20T13:35:35.720000+02:00 https://community.sap.com/t5/sap-cap-blog-posts/july-developer-challenge-week-3-multiplayer-amp-events/bc-p/14447107#M1074 Re: ๐Ÿ July Developer Challenge Week 3 โ€” Multiplayer & Events 2026-07-23T10:33:16.552000+02:00 ajay_soreng https://community.sap.com/t5/user/viewprofilepage/user-id/149819 <P><A href="https://community.sap.com/t5/user/viewprofilepage/user-id/155578" target="_blank">@shotokka</A>&nbsp;yes&nbsp;<a href="https://community.sap.com/t5/user/viewprofilepage/user-id/72">@Dan_Wroblewski</a>&nbsp; is right there is a ladder at position 2 that takes the player to 38.&nbsp;</P> 2026-07-23T10:33:16.552000+02:00