Save 37% off PRO during our Black Friday Sale! »

Building Board Games with Ruby

2f7eb8e439ce2d288dcfa240b5210664?s=47 Yonatan Bergman
September 19, 2014

Building Board Games with Ruby

Given @ GoGaruco 2014

Board games are seeing a huge resurgence right now, everywhere you look people are playing new games. The Internet and crowd funding has made it possible for almost anyone to design and develop a physical board game.

Join me while I share the story of how I used Ruby to design my first board game. How I honed the game mechanics and optimized all the different moving parts. You will leave this talk knowing the process of designing and developing a board game from scratch and how Ruby can help you iterate fast on new ideas using simple code, simulations and some statistics.

2f7eb8e439ce2d288dcfa240b5210664?s=128

Yonatan Bergman

September 19, 2014
Tweet

Transcript

  1. Building Board Games with Ruby

  2. Yonatan Bergman @YONBERGMAN

  3. Tech Lead eBay Israel Innovation Center

  4. 60+ GAMES* *not my collection

  5. Intro to board games

  6. Monopoly

  7. Risk

  8. Settlers of Catan

  9. Carcassonne

  10. Ticket to Ride

  11. Agricola

  12. Galaxy Trucker

  13. The Resistance

  14. None
  15. 2000 540 Games Published Per Year

  16. 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010

    2011 2012 2013 2000 540 547 614 759 850 956 943 974 987 1053 1100 1106 1118 1033 Games Published Per Year
  17. None
  18. None
  19. GAME DESIGNER

  20. GAME DESIGNER

  21. PUBLISHER

  22. DEVELOPMENT

  23. PLAYTESTING

  24. MANUFACTURING

  25. MANUFACTURING

  26. None
  27. Kickstarter

  28. 2010 2011 2012 2013 2014 H1 $30M $100K Kickstarter

  29. Print & Play

  30. None
  31. None
  32. None
  33. None
  34. None
  35. None
  36. None
  37. None
  38. None
  39. None
  40. None
  41. ATTACK BLOCK CHARGE

  42. ATTACK BLOCK CHARGE

  43. ATTACK BLOCK CHARGE

  44. ATTACK BLOCK CHARGE

  45. ATTACK BLOCK CHARGE

  46. None
  47. None
  48. None
  49. ! ! ! ! ! ! ! ! ! !

    ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ,''''', '''''' '''' :++++++ `++++++, '''' '''' ++++++++++++++ `++++++. '+' :++++++ ++++++ ++++ +++++++++: :+++++++++ ++++ ++++ ++++++++++++++ '+++++++++ +++++ :++++++ ++++++ ++++ +++++++++++. ,+++++++++++ ++++ ++++ ++++++++++++++ :+++++++++++ +++.'++ :++++++ `++++++ ++++ ;++++++++++++ ++++++++++++: ++++ ++++ ++++++++++++++ ++++++++++++. `+' ;+` :++++++` ;++++++ ++++ ++++++'++++++: ;+++++'+++++++ ++++ ++++ ++++++++++++++ '+++++'+++++++ '+ +' :++++++' +++++++ ++++ `++++ +++++ +++++ '++++ ++++ ++++ ++++++++++++++ ++++; +++++ ++ ++ :+++++++ +++++++ ++++ ;+++' `++++ ++++ ++++: ++++ ++++ ++++ ++++ ++++. ++ ++ :+++++++ +++++++ ++++ ++++. ++++ ++++ '+++' ++++ ++++ ++++ ++++ ++++: '+ ++ :+++++++ .+++++++ ++++ ++++. ++++ ++++ .++++ ++++ ++++ ++++ ++++ ;+++; .+. `+, :+++:+++: ++++++++ ++++ +++++ ++++ ++++ ++++ ++++ ++++ ++ ++ ,+++,++++ ++++++++ ++++ :+++++ +++++. ++++ ++++ ++++ +++++` ++ ,+' ,+++,++++ +++'++++ ++++ +++++++` +++++++; ++++ ++++ ++++ +++++++: ++.++ ,+++,++++ +++`++++ ++++ +++++++++' ,+++++++++. ++++ ++++ +++++++++++++` ;+++++++++` ++++ ,+++,.+++. :+++ ++++ ++++ `++++++++++' +++++++++++ ++++ ++++ +++++++++++++` +++++++++++ +++ ,+++, ++++ ++++ ++++ ++++ ;+++++++++++ +++++++++++` ++++ ++++ +++++++++++++` +++++++++++ ++++, ,+++, ++++ ++++ ++++ ++++ .++++++++++; +++++++++++ ++++ ++++ +++++++++++++` +++++++++++ +++ ++ ,+++, ++++ +++, ++++ ++++ .+++++++++ '++++++++; ++++ ++++ +++++++++++++` '++++++++, ;++ ;+; '. ,+++, ,+++``+++ ++++ ++++ '++++++, .+++++++ ++++ ++++ ++++ ,+++++++ ++ ++ +. ,+++, +++;'+++ ++++ ++++ '+++++ `+++++ ++++ ++++ ++++ .+++++ ;+. ,++ +` ,+++, ++++++++ ++++ ++++ ```` +++++ ```` ++++ ++++ ++++ ++++ ```` `++++ ++ ++ `+ ,+++, +++++++: ++++ ++++ ++++ ++++ ++++ ++++` ++++ ++++ ++++ ++++ ++++ ++ `++;+ ,+++, ;++++++ ++++ ++++ ++++ ++++ ++++` ++++ ++++ ++++ ++++ ++++ ++++ +' ++++ ,+++, ++++++ ++++ ++++ ++++ ++++ +++++ ++++ ++++ ++++ ++++ ++++' ++++ +' ++' ,+++, ++++++ ++++ ++++ +++++ ++++; ,++++ ++++ ++++ ++++ ++++ ;++++ .++++ ++ ++, ,+++, +++++' ++++ ++++ '+++++ +++++ +++++, .+++++ ++++ +++++++++++++` ++++++++++++++` +++++. ,+++++ ++ +++ ,+++, '++++` ++++ ++++ ++++++++++++++ ++++++++++++++. ++++ +++++++++++++` ++++++++++++++` ++++++++++++++ ;+, ++'+; ,+++, `++++ ++++ ++++ '++++++++++++. +++++++++++++ ++++ +++++++++++++` ++++++++++++++` +++++++++++++ ++ ;++ ++ ,+++, ++++ ++++ ++++ +++++++++++' .+++++++++++ ++++ +++++++++++++` ++++++++++++++` ,+++++++++++ ;++; `+++ ,+' ,+++, ++++ ++++ ++++ '++++++++: `+++++++++ ++++ +++++++++++++` ++++++++++++++` .+++++++++ '++++++ ++ ;++++: .++++'` .++++'` `;+'` ;+++: .++++` .+++' :+', ,+++++; ++++++ ++++ '+++++++: ++++++++++++` `++++++++ `++++++++ ++++: ++++; ++++, +++++++++++. ++++++++ ,++++++ ++++++ ++++ ++++++++++' +++++++++++++; ,++++++++++` :++++++++++ ++++: ++++; ++++, +++++++++++++ ++++++++++ ,++++++ ++++++ ++++ ++++++++++++: ++++++++++++++. `++++++++++++ `++++++++++++ ++++: ++++; ++++, ++++++++++++++ ++++++++++++ ,++++++ ,++++++ ++++ .+++++++++++++ +++++++++++++++ ++++++++++++++ +++++++++++++, ++++: ++++; ++++, ++++++++++++++` ;++++++++++++, ,++++++, +++++++ ++++ +++++++;++++++' +++++++++++++++` :++++++;'++++++` ;++++++;'++++++ ++++: ++++; ++++, +++++++++++++++ +++++. `'+++++ ,+++++++ +++++++ ++++ `+++++ +++++ ++++ ,++++' +++++. :+++++ +++++` +++++` ++++: ++++; ++++, ++++` +++++ ++++ :++++ ,+++++++ +++++++ ++++ +++++ `++++ ++++ +++++ `++++: '++++ .++++, +++++ ++++: ++++; ++++, ++++` ++++ `++++ ++++ ,+++++++ +++++++ ++++ ++++: ++++: ++++ :++++ +++++ ++++: +++++ ,++++ ++++: ++++; ++++, ++++` ++++. ,+++' ++++` ,+++++++` :+++++++ ++++ ++++ ,++++ ++++ .++++ ++++: +++++ ++++, ++++ ++++: ++++; ++++. ++++` '+++, ,++++ ''''. ,+++,+++' ++++++++ ++++ :++++ '''' ++++ .++++ ++++ `++++ ++++ '''' ++++: ++++; ++++. ++++` '+++: .++++` ,+++,++++ ++++++++ ++++ ++++' ++++ :+++' ++++ ++++ `++++ ++++: ++++; ++++. ++++` ++++, +++++' ,+++,++++ +++:++++ ++++ ++++, ++++ ++++` `++++ ++++ ,++++ ++++++++++++++++; ++++. ++++. ++++ ++++++++, ,+++,;+++ `+++ ++++ ++++ ++++` ++++ ;++++ ,++++ ++++ ;++++ ++++++++++++++++; ++++. ++++. :++++ :++++++++++ ,+++, +++: '+++ ++++ ++++ ++++ ++++++++++++++: :++++ ++++` '++++ ++++++++++++++++; ++++. ++++::::;++++++ +++++++++++. ,+++, ++++ ++++ ++++ ++++ ++++ +++++++++++++' ;++++ ++++` '+++' ++++++++++++++++; ++++. ++++++++++++++: +++++++++++` ,+++, ++++ +++' ++++ ++++ ++++ +++++++++++++` :++++ ++++` '++++ ++++++++++++++++; ++++. ++++++++++++++ ,++++++++++ ,+++, '+++ +++` ++++ ++++ ++++` ++++++++++++++ ,++++ ++++ :++++ ++++:```````++++; ++++. +++++++++++++ `++++++++, ,+++, `+++.,+++ ++++ ++++ ++++, ++++;;;;;++++++ `++++ ++++ ,++++ ++++: ++++; ++++. ++++++++++++ ;++++++ ,+++, ++++++++ ++++ ++++ ++++' ++++ ++++ :++++ ++++ ++++ ++++ ++++ ++++: ++++; ++++, ++++,....` +++++ ,+++, ++++++++ ++++ ++++ :++++ `++++ ++++ ++++` ++++ .++++ ++++ ++++ ++++: ++++; ++++, ++++` ++++ `++++ ,+++, +++++++. ++++ ++++ ++++ '++++ ++++ ++++, ++++; +++++ ++++: ++++ ++++: ++++; ++++, ++++` ++++ ++++ ,+++, ,++++++ ++++ ++++ ++++; ++++, ++++ ;+++; '++++ ++++, +++++ '++++ ++++: ++++; ++++, ++++` ++++ ++++ ,+++, ++++++ ++++ ++++ +++++ ,++++ ++++ ,+++' ++++' +++++ `++++; ++++; ++++: ++++; ++++, ++++` ++++' ++++ ,+++, ++++++ ++++ ++++ +++++ `+++++ ++++ `++++ +++++: '+++++ +++++: +++++ ++++: ++++; ++++, ++++` '++++` +++++ ,+++, +++++: ++++ ++++ ++++++++++++++, ++++ ++++ .++++++++++++++ ,++++++++++++++ ++++: ++++; ++++, ++++` ++++++.`:+++++` ,+++, :++++ ++++ ++++ +++++++++++++ ++++ ++++ ++++++++++++++ +++++++++++++ ++++: ++++; ++++, ++++` ++++++++++++++ ,+++, ++++ ++++ ++++ :+++++++++++ ++++ ++++ ++++++++++++ +++++++++++' ++++: ++++; ++++, ++++` ++++++++++++` ,+++, ++++ ++++ ++++ ;+++++++++. ++++ ++++ ++++++++++ ++++++++++ ++++: ++++; ++++, ++++` ++++++++++, ,+++, +++; ++++ ++++ `+++++++ ++++ ++++ +++++++' +++++++, ++++: ++++; ++++. ++++` ++++++++ .:. `,, `:, `,:`
  50. ! game = SimpleGame.new(players: 4) game.run > “Winner: ["#Player 2"]

    at turn number 31”

  51. ! game = SimpleGame.new(starting_energy: 10, 
 winning_energy: 20) game.run >

    “ --- TURN 1 --- “ 
 > “ #Player 1 (10) defends “
 > “ #Player 2 (10) attacks #Player 3 (10) “ 
 > “ #Player 3 (9) charges (10) “
 > …
 > “ --- TURN 47 --- “
 > “ #Player 1 (13) defends “
 > “ #Player 3 (19) charges (20) “
 > 
 > “ Winner: ["#Player 3"] at turn number 47 "
  52. ! games = MultiGames.new(games: 1000) games.simulate! ! game.average_turns
 > 24.821

    game.victory_count_per_player
 > “ [["Player 1", 314], “ 
 > “ ["Player 2", 354], “
 > “ ["Player 3", 340]] “

  53. ! class ActionResolver def resolve
 case player.action
 when :attack then

    resolve_attack
 when :charge then resolve_charge
 when :block then resolve_block
 end
 end def resolve_charge
 Result.new(player: +1)
 end end
  54. ! class RoundResolver def resolve
 @players.each do |player|
 resolver =

    ActionResolver.new(player)
 result = resolver.resolve()
 player.energy += result.player
 player.target.energy += result.target
 end
 end end
  55. ! class GameRound def run
 start_turn
 choose_cards
 reveal_aim_cards
 change_actions
 reveal_action_cards


    resolve_actions
 end_turn
 end end
  56. ! class ActionResolver def resolve
 case player.action
 when :attack then

    resolve_attack
 when :charge then resolve_charge
 when :block then resolve_block
 end
 end def resolve_charge
 Result.new(player: +1)
 end end
  57. ! class ActionResolver def resolve
 case player.action
 when :attack then

    resolve_attack
 when :charge then resolve_charge
 when :block then resolve_block
 end
 end def resolve_charge
 unless under_attack?
 Result.new(player: +1)
 end
 end
 end
  58. ! class ActionResolver class Result < Hashie::Dash
 property :player, default:

    0
 property :target, default: 0
 end . . . Result.new(player: +1, target: -1)
 
 end
  59. None
  60. None
  61. None
  62. None
  63. None
  64. ! class Player::Random < Player::Abstract def choose_aim
 targets.sample
 end def

    choose_action
 actions.sample
 end def change
 false
 end end
  65. ! class Player::Simple < Player::Random def choose_action
 if low_energy?
 [:block,

    :charge].sample
 else
 actions.sample
 end
 end def change
 targeted? ? :block : false
 end end
  66. ! games = MultiGames.new(players: [
 Player::Random.new,
 Player::Random.new, 
 Player::Simple.new,
 ])

    games.simulate! game.victory_count_per_player
 > “ [["Random 1", 30.08], “ 
 > “ ["Random 2", 31.93], “
 > “ ["Simple 3", 38.13]] “

  67. ! games = MultivariantGame.new games.simulate!
 > “...................“ games.save(“output.csv”)

  68. 2 1 5 4.0913 LMS 90.37% VP 9.63% 2 2

    5 6.0226 LMS 73.47% VP 26.53% 2 3 5 6.1922 LMS 50.30% VP 49.70% 2 4 5 4.0649 VP 77.52% LMS 22.48% 2 1 6 4.7287 LMS 93.21% VP 6.79% 2 2 6 7.4563 LMS 82.80% VP 17.20% 2 3 6 8.8977 LMS 66.31% VP 33.69% 2 4 6 8.2597 VP 53.56% LMS 46.44% 2 5 6 5.0287 VP 79.29% LMS 20.71% 2 1 7 5.0806 LMS 95.63% VP 4.37% 2 2 7 8.6032 LMS 87.94% VP 12.06% 2 3 7 11.1296 LMS 76.95% VP 23.05% 2 4 7 11.9691 LMS 61.34% VP 38.66% 2 5 7 10.266 VP 58.27% LMS 41.73% 2 6 7 5.9262 VP 80.90% LMS 19.10% 2 1 8 5.406 LMS 97.02% VP 2.98% 2 2 8 9.4609 LMS 91.88% VP 8.12% 2 3 8 12.9289 LMS 83.70% VP 16.30% 2 4 8 14.8757 LMS 72.81% VP 27.19% 2 5 8 15.0612 LMS 56.91% VP 43.09% 2 6 8 12.5056 VP 60.03% LMS 39.97% NUMBER OF PLAYERS STARTING ENERGY ENERGY TO WIN —————————— AVERAGE TURNS
  69. 2 1 5 4.0913 LMS 90.37% VP 9.63% 2 2

    5 6.0226 LMS 73.47% VP 26.53% 2 3 5 6.1922 LMS 50.30% VP 49.70% 2 4 5 4.0649 VP 77.52% LMS 22.48% 2 1 6 4.7287 LMS 93.21% VP 6.79% 2 2 6 7.4563 LMS 82.80% VP 17.20% 2 3 6 8.8977 LMS 66.31% VP 33.69% 2 4 6 8.2597 VP 53.56% LMS 46.44% 2 5 6 5.0287 VP 79.29% LMS 20.71% 2 1 7 5.0806 LMS 95.63% VP 4.37% 2 2 7 8.6032 LMS 87.94% VP 12.06% 2 3 7 11.1296 LMS 76.95% VP 23.05% 2 4 7 11.9691 LMS 61.34% VP 38.66% 2 5 7 10.266 VP 58.27% LMS 41.73% 2 6 7 5.9262 VP 80.90% LMS 19.10% 2 1 8 5.406 LMS 97.02% VP 2.98% 2 2 8 9.4609 LMS 91.88% VP 8.12% 2 3 8 12.9289 LMS 83.70% VP 16.30% 2 4 8 14.8757 LMS 72.81% VP 27.19% 2 5 8 15.0612 LMS 56.91% VP 43.09% 2 6 8 12.5056 VP 60.03% LMS 39.97%
  70. 2 1 5 4.0913 LMS 90.37% VP 9.63% 2 2

    5 6.0226 LMS 73.47% VP 26.53% 2 3 5 6.1922 LMS 50.30% VP 49.70% 2 4 5 4.0649 VP 77.52% LMS 22.48% 2 1 6 4.7287 LMS 93.21% VP 6.79% 2 2 6 7.4563 LMS 82.80% VP 17.20% 2 3 6 8.8977 LMS 66.31% VP 33.69% 2 4 6 8.2597 VP 53.56% LMS 46.44% 2 5 6 5.0287 VP 79.29% LMS 20.71% 2 1 7 5.0806 LMS 95.63% VP 4.37% 2 2 7 8.6032 LMS 87.94% VP 12.06% 2 3 7 11.1296 LMS 76.95% VP 23.05% 2 4 7 11.9691 LMS 61.34% VP 38.66% 2 5 7 10.266 VP 58.27% LMS 41.73% 2 6 7 5.9262 VP 80.90% LMS 19.10% 2 1 8 5.406 LMS 97.02% VP 2.98% 2 2 8 9.4609 LMS 91.88% VP 8.12% 2 3 8 12.9289 LMS 83.70% VP 16.30% 2 4 8 14.8757 LMS 72.81% VP 27.19% 2 5 8 15.0612 LMS 56.91% VP 43.09% 2 6 8 12.5056 VP 60.03% LMS 39.97%
  71. AVG TURNS ENERGY TO WIN STARTING ENERGY

  72. AVG TURNS ENERGY TO WIN STARTING ENERGY

  73. AVG TURNS ENERGY TO WIN STARTING ENERGY

  74. AVG TURNS ENERGY TO WIN STARTING ENERGY

  75. Winning Energy Starting Energy Zero Energy

  76. AVG TURNS NUMBER OF PLAYERS

  77. AVG TURNS NUMBER OF PLAYERS

  78. AVG TURNS NUMBER OF PLAYERS

  79. 2 1 5 4.0913 LMS 90.37% VP 9.63% 2 2

    5 6.0226 LMS 73.47% VP 26.53% 2 3 5 6.1922 LMS 50.30% VP 49.70% 2 4 5 4.0649 VP 77.52% LMS 22.48% 2 1 6 4.7287 LMS 93.21% VP 6.79% 2 2 6 7.4563 LMS 82.80% VP 17.20% 2 3 6 8.8977 LMS 66.31% VP 33.69% 2 4 6 8.2597 VP 53.56% LMS 46.44% 2 5 6 5.0287 VP 79.29% LMS 20.71% 2 1 7 5.0806 LMS 95.63% VP 4.37% 2 2 7 8.6032 LMS 87.94% VP 12.06% 2 3 7 11.1296 LMS 76.95% VP 23.05% 2 4 7 11.9691 LMS 61.34% VP 38.66% 2 5 7 10.266 VP 58.27% LMS 41.73% 2 6 7 5.9262 VP 80.90% LMS 19.10% 2 1 8 5.406 LMS 97.02% VP 2.98% 2 2 8 9.4609 LMS 91.88% VP 8.12% 2 3 8 12.9289 LMS 83.70% VP 16.30% 2 4 8 14.8757 LMS 72.81% VP 27.19% 2 5 8 15.0612 LMS 56.91% VP 43.09% 2 6 8 12.5056 VP 60.03% LMS 39.97%
  80. ! class ActionResolver def resolve_block
 Result.new(player: -1) unless under_attack?
 end

    end
  81. 2 1 5 4.0913 LMS 90.37% VP 9.63% 2 2

    5 6.0226 LMS 73.47% VP 26.53% 2 3 5 6.1922 LMS 50.30% VP 49.70% 2 4 5 4.0649 VP 77.52% LMS 22.48% 2 1 6 4.7287 LMS 93.21% VP 6.79% 2 2 6 7.4563 LMS 82.80% VP 17.20% 2 3 6 8.8977 LMS 66.31% VP 33.69% 2 4 6 8.2597 VP 53.56% LMS 46.44% 2 5 6 5.0287 VP 79.29% LMS 20.71% 2 1 7 5.0806 LMS 95.63% VP 4.37% 2 2 7 8.6032 LMS 87.94% VP 12.06% 2 3 7 11.1296 LMS 76.95% VP 23.05% 2 4 7 11.9691 LMS 61.34% VP 38.66% 2 5 7 10.266 VP 58.27% LMS 41.73% 2 6 7 5.9262 VP 80.90% LMS 19.10% 2 1 8 5.406 LMS 97.02% VP 2.98% 2 2 8 9.4609 LMS 91.88% VP 8.12% 2 3 8 12.9289 LMS 83.70% VP 16.30% 2 4 8 14.8757 LMS 72.81% VP 27.19% 2 5 8 15.0612 LMS 56.91% VP 43.09% 2 6 8 12.5056 VP 60.03% LMS 39.97%
  82. None
  83. None
  84. None
  85. None
  86. None
  87. None
  88. None
  89. ! class Player::SingleMinded < Player::Abstract def choose_action
 return @action
 end

    end
  90. ! games = MultiGames.new(players: [
 Player::Simple.new,
 Player::Simple.new, 
 Player::SingleMinded.new(action: :block),


    ]) games.simulate! game.victory_count_per_player
 > “ [["Simple 1", 44.95], “ 
 > “ ["Simple 2", 45.28], “
 > “ [“Single Minded block", 9.77]] “

  91. None
  92. WRITING RULES IS HARD

  93. None
  94. None
  95. None
  96. None
  97. ! games = MultiGames.new(players: [
 Player::Simple.new,
 Player::Simple.new, 
 Player::SingleMinded.new(action: :block),


    ]) games.simulate! game.victory_count_per_player
 > “ [["Simple 1", 44.95], “ 
 > “ ["Simple 2", 45.28], “
 > “ [“Single Minded block", 9.77]] “
 yonbergman/missiles_and_microchips
  98. None
  99. GIVEAWAY ! ! My first computer #gogaruco #microchips

  100. @YONBERGMAN