Last active
November 24, 2016 16:16
-
-
Save geoffreywiseman/3ef7a7b4f9d6521b237ed559114a425a to your computer and use it in GitHub Desktop.
Game simulation of 4-way countdown, one in Ruby, one in Swift.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" ) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I was surprised that the Ruby version runs twice as fast as the Swift? Probably pretty io-bound, but still surprised.