Skip to content

Instantly share code, notes, and snippets.

@geoffreywiseman
Last active November 24, 2016 16:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save geoffreywiseman/3ef7a7b4f9d6521b237ed559114a425a to your computer and use it in GitHub Desktop.
Save geoffreywiseman/3ef7a7b4f9d6521b237ed559114a425a to your computer and use it in GitHub Desktop.
Game simulation of 4-way countdown, one in Ruby, one in Swift.
class Game
def initialize()
@turn_index = 0
@players = [Player.new(1), Player.new(2)]
@players.first.set_opponent(@players.last)
@players.last.set_opponent(@players.first)
end
def take_turn
print( "#{turn}, p#{current_player.number}: " )
current_player.take_turn
print( "#{current_player} won!\n" ) if current_player.won?
@turn_index += 1
end
def over?
@players.any? { |p| p.won? }
end
def current_player
@players[ @turn_index % 2 ]
end
def turn
@turn_index + 1
end
end
class Player
attr_accessor :number
def initialize( player_number )
@number = player_number
@board = Array.new(10)
@opponent = nil
reset_board
end
def to_s
"Player #{@number}"
end
def set_opponent( player )
@opponent = player
end
def take_turn
print("board:[")
print( @board.each_with_index.map { |up,index| up ? "*" : "#{index+1}" }.join( "," ) )
print("] ")
dice = Dice.roll
if dice.sum?(12)
print("rolled #{dice}: reset self\n")
self.reset_board
elsif dice.sum?(11)
print( "rolled #{dice}: reset opponent\n")
@opponent.reset_board
else
flip(dice)
end
end
def flip(dice)
combos = dice.combos.select(&method(:valid_move))
if combos.empty?
print( "rolled #{dice}, no move\n" )
else
@board[combos.first-1]=true
print( "rolled #{dice}, moves: #{combos}, flipped #{combos.first}\n" )
end
end
def reset_board
@board.fill(false)
end
def valid_move(move)
move > 0 && move <= 10 && @board[move-1] == false
end
def won?
@board.all? { |pos| pos }
end
end
class Dice
@random = Random.new
def self.roll( )
values = Array.new
(0..1).each do
values << @random.rand(6) + 1
end
Dice.new( values )
end
def initialize( values )
@values = values
end
def to_s
@values.join(",")
end
def combos
combos = [@values.first + @values.last, @values.first * @values.last, @values.first - @values.last, @values.last - @values.first]
add_integer_div( combos, @values.first, @values.last )
add_integer_div( combos, @values.last, @values.first )
combos.uniq
end
def add_integer_div( combos, dividend, divisor )
result = dividend.divmod(divisor)
combos << result.first if result.last == 0
end
def sum?( sum )
@values.inject(:+) == sum
end
end
games = 10000
turns = []
(0..games-1).each do
game = Game.new
until game.over?
game.take_turn
end
print( "Game over. Game took #{game.turn} turns.\n")
turns << game.turn
end
min_turns = turns.min
mean_turns = turns.inject(:+).to_f/games
max_turns = turns.max
print( "Over #{games} games, turns was #{min_turns} - #{max_turns}, averaging #{mean_turns}\n" )
import Darwin
class Game {
var turnIndex = 0
let players = [Player(1), Player(2)]
var currentPlayer: Player {
return players[ turnIndex % 2 ]
}
var over: Bool {
return players.contains { player in player.won }
}
var turn: Int {
return turnIndex + 1
}
init() {
if let firstPlayer = players.first, let secondPlayer = players.last {
firstPlayer.opponent = secondPlayer
secondPlayer.opponent = firstPlayer
}
}
func takeTurn() {
print( "\(turn), p#\(currentPlayer.number): ", terminator: "" )
currentPlayer.takeTurn()
if( currentPlayer.won ) {
print( "\(currentPlayer) won!")
}
turnIndex += 1
}
}
class Player : CustomStringConvertible {
let number: Int
var opponent: Player?
var board: [Bool]
var description: String { return "Player \(number)" }
var won: Bool { return !board.contains(false) }
init( _ playerNumber: Int ) {
number = playerNumber
board = Array(repeating: false, count: 10)
}
func takeTurn() {
let boardDesc = board
.enumerated()
.map { (index, value) in value ? "*" : "\(index)" }
.joined( separator: "," )
print( "board: [\(boardDesc)] ", terminator: "" )
let dice = Dice.roll2d6()
if( dice.sum == 12 ) {
print( "rolled \(dice): reset self (\(self))" )
self.resetBoard()
} else if( dice.sum == 11 ) {
print( "rolled \(dice): reset opponent (\(opponent))" )
opponent?.resetBoard()
} else {
flip(dice)
}
}
func flip( _ dice: Dice ) {
let moves = dice.moves().filter(validMove)
if let move = moves.first {
board[ move - 1 ] = true
print( "rolled \(dice), moves \(moves), flipped \(move)" )
} else {
print( "rolled \(dice), no move" )
}
}
func resetBoard() {
board = Array(repeating: false, count: 10)
}
func validMove( move: Int ) -> Bool {
return move > 0 && move <= 10 && board[ move-1 ] == false
}
}
class Dice : CustomStringConvertible {
let values: [Int]
var description: String {
return values
.map { String($0) }
.joined( separator: "," )
}
var sum: Int { return values.reduce( 0, + ) }
init(_ values: Int...) {
self.values = values
}
class func roll2d6() -> Dice {
return Dice(rolld6(), rolld6())
}
class func rolld6() -> Int {
return Int(arc4random_uniform(6)) + 1
}
func moves() -> Set<Int> {
if let first = values.first, let last = values.last {
var moves: Set<Int> = [
first + last,
first * last,
first - last,
last - first
]
if let result = integerQuotient( moves, dividend: first, divisor: last ) {
moves.insert(result)
}
if let result = integerQuotient( moves, dividend: last, divisor: first ) {
moves.insert(result)
}
return moves
} else {
return Set<Int>()
}
}
func integerQuotient( _ collection: Set<Int>, dividend:Int, divisor:Int ) -> Int? {
guard dividend % divisor == 0 else { return nil }
return dividend / divisor
}
}
var turnsPerGame = Array<Int>()
for index in 1...10000 {
let game = Game()
repeat {
game.takeTurn()
} while( !game.over )
print( "Game over. Game took \(game.turn) turns." )
turnsPerGame.append( game.turn )
}
if let minTurns = turnsPerGame.min(), let maxTurns = turnsPerGame.max() {
let meanTurns = Float(turnsPerGame.reduce(0,+))/Float(turnsPerGame.count)
print( "Over \(turnsPerGame.count) games, turns was \(minTurns) - \(maxTurns), averaging \(meanTurns)\n" )
}
@geoffreywiseman
Copy link
Author

I was surprised that the Ruby version runs twice as fast as the Swift? Probably pretty io-bound, but still surprised.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment