SlideShare une entreprise Scribd logo
1  sur  49
Building MapAttack: A
real-time geolocation
         game
      Kyle Drake
geoloqi.com




              What is Geoloqi?




geoloqi.com
geoloqi.com




      Real-world solutions
for thousands around the globe
geoloqi.com




 Real-Time Location Tracking


Real-World Interactive Experiences
geoloqi.com




Personal Location
geoloqi.com




Location-Based Notes
geoloqi.com



YOUR GPS GETS WEIRD.




     WE HELP FIX THAT.
geoloqi.com




Real-Time Applications
geoloqi.com
geoloqi.com




First Hackathon Attempt
    Layer in Geoloqi

     Updates to MapAttack server
via HTTP REST API

     Processing through Geoloqi’s
persistent store

    Finished in a weekend

    A lot of fun!
geoloqi.com




There were some kinks
   Used an REST HTTP service for
APNS (worked, but not “real-time”)

     Server was getting backed up
(updates were not async)
geoloqi.com




  MapAttack Server Issues
     Blocking IO (requests to Geoloqi API held
everything up)

     Lots of concurrent traffic

      Events had to be broadcast to all phones/
clients in the game synchronously
geoloqi.com




 HOW WE
DEALT WITH
    IT
geoloqi.com
geoloqi.com




A key-value store, and so much more

A great implementation of PUBLISH/SUBSCRIBE

So simple, you can actually understand it

Very fast and reliable

Robust library support
geoloqi.com
geoloqi.com
geoloqi.com
geoloqi.com




    HOW WE USE NODE JS


• Phones:
Custom binary protocol over UDP

• Web Browsers:
Web Sockets via Socket.IO
geoloqi.com




Socket.IO is awesome
 Adapter Pattern for realtime

 Web Sockets, Flash, Long Polling!

 One interface for everything

 Easy to implement: ~100 LOC
geoloqi.com
geoloqi.com




   Geoloqi API
ASYNC FTW!

Delayed updates when possible

Messages queues: Beanstalk

Workers pick tasks off the stack

Process data outside of the persistent store

Simpler than AMQP
geoloqi.com
geoloqi.com



       MapAttack Server
  (or: How to make Ruby ROFLscale)

MRI Ruby has a global interpreter lock

But MRI Ruby does not block on IO

JRuby and Rubinius 2: Native Threads

And it has a reactor pattern, just like Node JS!
geoloqi.com



         The Reactor Pattern
   “The reactor design pattern is a concurrent
    programming pattern for handling service
   requests delivered concurrently to a service
   handler by one or more inputs” - Wikipedia
 My simplest description: Takes your blocking IO
  operation, shoves it into its own kernel thread
behind the scenes, uses Unix kernel magic to make
    it rejoin the reactor queue when it’s ready.
                 Linux: epoll(4)
                 BSD: kqueue/kevent
geoloqi.com



Blocking IO a UNIVERSAL problem
All programming languages have trouble with it
The Reactor pattern is a tool to resolve it
Most languages have the Reactor pattern!

JavaScript                     Node.js

 Python                        Twisted

  Ruby            EventMachine (libem, C)

   Java                    JBoss_Netty
             (wait, what? I thought Java had good threading..)

  PHP             None yet (perhaps ever)
geoloqi.com




Sinatra::Synchrony
   kyledrake.net/sinatra-synchrony

Implements EventMachine with Fibers

The result: NO CALLBACKS!

< 100 LOC

Only coding change is to use EM libs and fibers

WOW, THAT WAS EASY.
geoloqi.com




Sinatra::Synchrony
geoloqi.com
geoloqi.com




Let’s Talk Performance.
geoloqi.com




Make the relevant data
stay outside of the slow
   persistence store.
geoloqi.com




If you’re trying to solve a problem by
  ROFLscaling your persistent store,
         you’re doing it wrong.
geoloqi.com




        Examples of doing it wrong
     “Let’s rewrite everything to use TrendDB”

     Using EC2 for a single-master DB “because it scales”

     Buying something from Oracle

    “Ruby/PHP/JS is too slow for this, let’s rewrite it in
TrendLang”

     Most NoSQL solutions have dark secrets you don’t
know about. Global write locks, single write master, et
cetera
geoloqi.com




If you must speed up your persistent
   store, here’s my practical advice.
geoloqi.com



                   Real Hardware FTW?
        Single master database? Get a real server! Really!

        Cloud is only as fast as the fastest available HDD

        EBS performance is not great, even in RAID0 stripe
See orion.heroku.com/past/2009/7/29/io_performance_on_ebs
and perfcap.blogspot.com/2011/03/understanding-and-using-amazon-ebs.html
and blog.dt.org/index.php/2010/06/amazon-ec2-io-performance-local-emphemeral-disks-vs-raid0-
striped-ebs-volumes/
and endevver.com/2010/03/cost-analysis-of-an-amazon-ec2-deployment.html
and mysqlperformanceblog.com/2011/02/21/death-match-ebs-versus-ssd-price-performance-and-
qos
and google.com/search?q=heroku+ebs+performance


        High end Xeon, 32GB ECC, fast 220GB SSD: ~$2K
geoloqi.com



            Real Hardware FTW?
The paradigm is diversifying, and the reason is SSD.

Say hello to the Fusion-io ioDrive Octal.
PCIe x16 slot
5.12TB
6.0 GB/s read
4.4 GB/s write
1.19 MILLION IOPS

Cost: $100,000 (plus $10 S&H).
It’s a steal at this price.. but it will get cheaper.
geoloqi.com



Real Hardware FTW?
  $500 PCIe SSD drives:
        220GB
     >100,000 IOPS
     ~700MB/s R/W

  15,000 RPM SAS drives:
          2TB+
      ~175-210 IOPS
      <150MB/s R/W
geoloqi.com



               Long Term
    Multi-master experiment (Brewer’s CAP)

    Riak is my current favorite

     Cloud companies providing real hardware
performance
geoloqi.com




In Summary
geoloqi.com




Geo-location games
     are FUN!
geoloqi.com



 Use the Geoloqi API to make
geolocation games! We did the
      hard work for you.
geoloqi.com



MapAttack is open source!
   github.com/geoloqi/MapAttack
Text
geoloqi.com



Other uses of Geoloqi
geoloqi.com




Thanks!

Contenu connexe

Tendances

Infrastructure as code might be literally impossible part 2
Infrastructure as code might be literally impossible part 2Infrastructure as code might be literally impossible part 2
Infrastructure as code might be literally impossible part 2
ice799
 

Tendances (20)

Rubykaigi 2017-nishimotz-v6
Rubykaigi 2017-nishimotz-v6Rubykaigi 2017-nishimotz-v6
Rubykaigi 2017-nishimotz-v6
 
Devignition 2011
Devignition 2011Devignition 2011
Devignition 2011
 
Infrastructure as code might be literally impossible part 2
Infrastructure as code might be literally impossible part 2Infrastructure as code might be literally impossible part 2
Infrastructure as code might be literally impossible part 2
 
Ruby 1.9 Fibers
Ruby 1.9 FibersRuby 1.9 Fibers
Ruby 1.9 Fibers
 
Actors and Threads
Actors and ThreadsActors and Threads
Actors and Threads
 
Shall we play a game?
Shall we play a game?Shall we play a game?
Shall we play a game?
 
Scaling Ruby with Evented I/O - Ruby underground
Scaling Ruby with Evented I/O - Ruby undergroundScaling Ruby with Evented I/O - Ruby underground
Scaling Ruby with Evented I/O - Ruby underground
 
About Clack
About ClackAbout Clack
About Clack
 
Realtime web2012
Realtime web2012Realtime web2012
Realtime web2012
 
Puppet Camp LA 2/19/2015
Puppet Camp LA 2/19/2015Puppet Camp LA 2/19/2015
Puppet Camp LA 2/19/2015
 
Ruby Proxies for Scale, Performance, and Monitoring
Ruby Proxies for Scale, Performance, and MonitoringRuby Proxies for Scale, Performance, and Monitoring
Ruby Proxies for Scale, Performance, and Monitoring
 
Package manages and Puppet - PuppetConf 2015
Package manages and Puppet - PuppetConf 2015Package manages and Puppet - PuppetConf 2015
Package manages and Puppet - PuppetConf 2015
 
Pyramid faq
Pyramid faqPyramid faq
Pyramid faq
 
Spacebrew @ SFPC
Spacebrew @ SFPCSpacebrew @ SFPC
Spacebrew @ SFPC
 
No Callbacks, No Threads - RailsConf 2010
No Callbacks, No Threads - RailsConf 2010No Callbacks, No Threads - RailsConf 2010
No Callbacks, No Threads - RailsConf 2010
 
How to-node-core
How to-node-coreHow to-node-core
How to-node-core
 
Chef Conf 2015: Package Management & Chef
Chef Conf 2015: Package Management & ChefChef Conf 2015: Package Management & Chef
Chef Conf 2015: Package Management & Chef
 
An introduction and future of Ruby coverage library
An introduction and future of Ruby coverage libraryAn introduction and future of Ruby coverage library
An introduction and future of Ruby coverage library
 
Node.js and Ruby
Node.js and RubyNode.js and Ruby
Node.js and Ruby
 
Online game server on Akka.NET (NDC2016)
Online game server on Akka.NET (NDC2016)Online game server on Akka.NET (NDC2016)
Online game server on Akka.NET (NDC2016)
 

Similaire à Building MapAttack

Phonegap for Engineers
Phonegap for EngineersPhonegap for Engineers
Phonegap for Engineers
Brian LeRoux
 
Cannibalising The Google App Engine
Cannibalising The  Google  App  EngineCannibalising The  Google  App  Engine
Cannibalising The Google App Engine
catherinewall
 

Similaire à Building MapAttack (20)

Java: Rumours of my demise are greatly exaggerated
Java: Rumours of my demise are greatly exaggeratedJava: Rumours of my demise are greatly exaggerated
Java: Rumours of my demise are greatly exaggerated
 
RubyMotion Inspect Conference - 2013. (Without speaker notes.)
RubyMotion Inspect Conference - 2013. (Without speaker notes.)RubyMotion Inspect Conference - 2013. (Without speaker notes.)
RubyMotion Inspect Conference - 2013. (Without speaker notes.)
 
JavaScript Libraries (@Media)
JavaScript Libraries (@Media)JavaScript Libraries (@Media)
JavaScript Libraries (@Media)
 
OSGI,
OSGI,OSGI,
OSGI,
 
cadec-2017-golang
cadec-2017-golangcadec-2017-golang
cadec-2017-golang
 
Getting started with Emscripten – Transpiling C / C++ to JavaScript / HTML5
Getting started with Emscripten – Transpiling C / C++ to JavaScript / HTML5Getting started with Emscripten – Transpiling C / C++ to JavaScript / HTML5
Getting started with Emscripten – Transpiling C / C++ to JavaScript / HTML5
 
Java EE 7 Soup to Nuts at JavaOne 2014
Java EE 7 Soup to Nuts at JavaOne 2014Java EE 7 Soup to Nuts at JavaOne 2014
Java EE 7 Soup to Nuts at JavaOne 2014
 
Introduction to Node.js
Introduction to Node.jsIntroduction to Node.js
Introduction to Node.js
 
JavaScript Libraries (Kings of Code)
JavaScript Libraries (Kings of Code)JavaScript Libraries (Kings of Code)
JavaScript Libraries (Kings of Code)
 
Phonegap for Engineers
Phonegap for EngineersPhonegap for Engineers
Phonegap for Engineers
 
JavaScript Library Overview
JavaScript Library OverviewJavaScript Library Overview
JavaScript Library Overview
 
Cannibalising The Google App Engine
Cannibalising The  Google  App  EngineCannibalising The  Google  App  Engine
Cannibalising The Google App Engine
 
Google App Engine Java, Groovy and Gaelyk
Google App Engine Java, Groovy and GaelykGoogle App Engine Java, Groovy and Gaelyk
Google App Engine Java, Groovy and Gaelyk
 
Deploy Python apps in 5 min with a PaaS
Deploy Python apps in 5 min with a PaaSDeploy Python apps in 5 min with a PaaS
Deploy Python apps in 5 min with a PaaS
 
Developing realtime apps with Drupal and NodeJS
Developing realtime apps with Drupal and NodeJS Developing realtime apps with Drupal and NodeJS
Developing realtime apps with Drupal and NodeJS
 
Managing large and distributed Eclipse server applications.
Managing large and distributed Eclipse server applications.Managing large and distributed Eclipse server applications.
Managing large and distributed Eclipse server applications.
 
The State of WebSockets in Django
The State of WebSockets in DjangoThe State of WebSockets in Django
The State of WebSockets in Django
 
Robotic JavaScript
Robotic JavaScriptRobotic JavaScript
Robotic JavaScript
 
Guides To Analyzing WebKit Performance
Guides To Analyzing WebKit PerformanceGuides To Analyzing WebKit Performance
Guides To Analyzing WebKit Performance
 
Evented Ruby VS Node.js
Evented Ruby VS Node.jsEvented Ruby VS Node.js
Evented Ruby VS Node.js
 

Dernier

Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Victor Rentea
 

Dernier (20)

FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
Cyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdfCyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdf
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectors
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 

Building MapAttack

  • 1. Building MapAttack: A real-time geolocation game Kyle Drake
  • 2. geoloqi.com What is Geoloqi? geoloqi.com
  • 3. geoloqi.com Real-world solutions for thousands around the globe
  • 4. geoloqi.com Real-Time Location Tracking Real-World Interactive Experiences
  • 7. geoloqi.com YOUR GPS GETS WEIRD. WE HELP FIX THAT.
  • 10. geoloqi.com First Hackathon Attempt Layer in Geoloqi Updates to MapAttack server via HTTP REST API Processing through Geoloqi’s persistent store Finished in a weekend A lot of fun!
  • 11.
  • 12.
  • 13.
  • 14.
  • 15. geoloqi.com There were some kinks Used an REST HTTP service for APNS (worked, but not “real-time”) Server was getting backed up (updates were not async)
  • 16. geoloqi.com MapAttack Server Issues Blocking IO (requests to Geoloqi API held everything up) Lots of concurrent traffic Events had to be broadcast to all phones/ clients in the game synchronously
  • 19. geoloqi.com A key-value store, and so much more A great implementation of PUBLISH/SUBSCRIBE So simple, you can actually understand it Very fast and reliable Robust library support
  • 23. geoloqi.com HOW WE USE NODE JS • Phones: Custom binary protocol over UDP • Web Browsers: Web Sockets via Socket.IO
  • 24. geoloqi.com Socket.IO is awesome Adapter Pattern for realtime Web Sockets, Flash, Long Polling! One interface for everything Easy to implement: ~100 LOC
  • 26. geoloqi.com Geoloqi API ASYNC FTW! Delayed updates when possible Messages queues: Beanstalk Workers pick tasks off the stack Process data outside of the persistent store Simpler than AMQP
  • 28. geoloqi.com MapAttack Server (or: How to make Ruby ROFLscale) MRI Ruby has a global interpreter lock But MRI Ruby does not block on IO JRuby and Rubinius 2: Native Threads And it has a reactor pattern, just like Node JS!
  • 29. geoloqi.com The Reactor Pattern “The reactor design pattern is a concurrent programming pattern for handling service requests delivered concurrently to a service handler by one or more inputs” - Wikipedia My simplest description: Takes your blocking IO operation, shoves it into its own kernel thread behind the scenes, uses Unix kernel magic to make it rejoin the reactor queue when it’s ready. Linux: epoll(4) BSD: kqueue/kevent
  • 30. geoloqi.com Blocking IO a UNIVERSAL problem All programming languages have trouble with it The Reactor pattern is a tool to resolve it Most languages have the Reactor pattern! JavaScript Node.js Python Twisted Ruby EventMachine (libem, C) Java JBoss_Netty (wait, what? I thought Java had good threading..) PHP None yet (perhaps ever)
  • 31. geoloqi.com Sinatra::Synchrony kyledrake.net/sinatra-synchrony Implements EventMachine with Fibers The result: NO CALLBACKS! < 100 LOC Only coding change is to use EM libs and fibers WOW, THAT WAS EASY.
  • 35. geoloqi.com Make the relevant data stay outside of the slow persistence store.
  • 36. geoloqi.com If you’re trying to solve a problem by ROFLscaling your persistent store, you’re doing it wrong.
  • 37. geoloqi.com Examples of doing it wrong “Let’s rewrite everything to use TrendDB” Using EC2 for a single-master DB “because it scales” Buying something from Oracle “Ruby/PHP/JS is too slow for this, let’s rewrite it in TrendLang” Most NoSQL solutions have dark secrets you don’t know about. Global write locks, single write master, et cetera
  • 38. geoloqi.com If you must speed up your persistent store, here’s my practical advice.
  • 39. geoloqi.com Real Hardware FTW? Single master database? Get a real server! Really! Cloud is only as fast as the fastest available HDD EBS performance is not great, even in RAID0 stripe See orion.heroku.com/past/2009/7/29/io_performance_on_ebs and perfcap.blogspot.com/2011/03/understanding-and-using-amazon-ebs.html and blog.dt.org/index.php/2010/06/amazon-ec2-io-performance-local-emphemeral-disks-vs-raid0- striped-ebs-volumes/ and endevver.com/2010/03/cost-analysis-of-an-amazon-ec2-deployment.html and mysqlperformanceblog.com/2011/02/21/death-match-ebs-versus-ssd-price-performance-and- qos and google.com/search?q=heroku+ebs+performance High end Xeon, 32GB ECC, fast 220GB SSD: ~$2K
  • 40. geoloqi.com Real Hardware FTW? The paradigm is diversifying, and the reason is SSD. Say hello to the Fusion-io ioDrive Octal. PCIe x16 slot 5.12TB 6.0 GB/s read 4.4 GB/s write 1.19 MILLION IOPS Cost: $100,000 (plus $10 S&H). It’s a steal at this price.. but it will get cheaper.
  • 41. geoloqi.com Real Hardware FTW? $500 PCIe SSD drives: 220GB >100,000 IOPS ~700MB/s R/W 15,000 RPM SAS drives: 2TB+ ~175-210 IOPS <150MB/s R/W
  • 42. geoloqi.com Long Term Multi-master experiment (Brewer’s CAP) Riak is my current favorite Cloud companies providing real hardware performance
  • 45. geoloqi.com Use the Geoloqi API to make geolocation games! We did the hard work for you.
  • 46. geoloqi.com MapAttack is open source! github.com/geoloqi/MapAttack
  • 47. Text

Notes de l'éditeur

  1. \n
  2. \n
  3. \n
  4. \n
  5. \n
  6. \n
  7. \n
  8. \n
  9. \n
  10. \n
  11. \n
  12. \n
  13. \n
  14. \n
  15. \n
  16. \n
  17. \n
  18. \n
  19. \n
  20. \n
  21. \n
  22. \n
  23. \n
  24. \n
  25. \n
  26. \n
  27. \n
  28. \n
  29. \n
  30. \n
  31. \n
  32. \n
  33. \n
  34. \n
  35. \n
  36. \n
  37. \n
  38. \n
  39. \n
  40. \n
  41. \n
  42. \n
  43. \n
  44. \n
  45. \n
  46. \n
  47. \n
  48. \n
  49. \n