Spectacular Seven

May 4, 2010

There’s nothing hard about this program as long as you keep straight what you are doing at each step:

(define (seven n)
  (let ((wins (make-vector 8 0))
        (scores (make-vector 8 0)))
    (let loop ((k n) (p 0) (winner 0) (challenger 1)
               (queue '(2 3 4 5 6 7)))
      (cond ((zero? k) ; simulation ends
              (map (lambda (x) (exact->inexact (/ x n 1/100)))
                   (vector->list wins)))
            ((<= 7 (vector-ref scores winner)) ; game ends
              (vector-set! wins winner
                (+ (vector-ref wins winner) 1))
              (set! scores (make-vector 8 0))
              (loop (- k 1) 0 0 1 '(2 3 4 5 6 7)))
            ((< (rand) 1/2) ; current winner wins point
              (vector-set! scores winner
                (+ (vector-ref scores winner)
                   (if (<= 7 p) 2 1)))
              (loop k (+ p 1) winner (car queue)
                    (append (cdr queue) (list challenger))))
            (else ; current challenger wins point
              (vector-set! scores challenger
                (+ (vector-ref scores challenger)
                   (if (<= 7 p) 2 1)))
              (loop k (+ p 1) challenger (car queue)
                    (append (cdr queue) (list winner))))))))

The wins vector tracks which team won; at the end of the simulation, the sum of the wins over all teams should equal n. The scores vector tracks the scores of each team in a single game, and is reset at the beginning of each game; p keeps track of the point count in each game, to determine if a particular point has a value of 1 or 2. Here are the winning percentages we get after a million simulated games:

> (seven 1000000)
(16.3057 16.2836 13.8705 12.4255 10.2218 10.2573 8.877 11.7586)

The first two teams have similar winning percentages; since it is fifty/fifty that either wins the first point, that makes sense. Winning percentages decrease for each succeeding team until the last, who can win the game if they win the four points they play, with 1 on the first point and 2 on each of the next three points. In real jai alai games, handicappers put the best teams in the fourth, fifth and sixth post positions to make games more exciting.

We used rand from the Standard Prelude. You can run the program at http://programmingpraxis.codepad.org/6bcXRB4Z.

Pages: 1 2

5 Responses to “Spectacular Seven”

  1. […] Praxis – Spectacular Seven By Remco Niemeijer In today’s Programming Praxis exercise our task is to run a simulation of a ballgame to see if the scoring […]

  2. Remco Niemeijer said

    My Haskell solution (see http://bonsaicode.wordpress.com/2010/05/04/programming-praxis-spectacular-seven/ for a version with comments):

    import Control.Applicative
    import Control.Monad
    import Data.List
    import System.Random
    
    match :: Int -> [(a, Int)] -> Int -> [(a, Int)]
    match ps ~(x:y:r) w = (p,s + if ps > 7 then 2 else 1) : r ++ [c]
        where ((p,s), c) = if w == 0 then (x,y) else (y,x)
    
    game :: IO Int
    game = f 0 (zip [1..8] [0,0..]) . randomRs (0,1) <$> newStdGen
           where f ps a ~(x:xs) = maybe (f (ps+1) (match ps a x) xs) fst $
                                  find ((>= 7) . snd) a
    
    simulate :: Int -> IO [Float]
    simulate n = (\ws -> map (\x -> 100 * (l x - 1) / l ws) . group .
                         sort $ ws ++ [1..8]) <$> replicateM n game
                 where l = fromIntegral . length
    
  3. Gambiteer said
    ;; for some reason a circular buffer seems most natural
    
    (define (seven-2 n)
      (let ((teams (list 0 1 2 3 4 5 6 7))
    	(scores (make-vector 8 0))
    	(wins   (make-vector 8 0)))
    
        (define last-pair
          (lambda (x)
    	(if (pair? (cdr x))
    	    (last-pair (cdr x))
    	    x)))
        
        (define (team-score team)
          (vector-ref scores team))
    
        (define (increment-wins! team)
          (vector-set! wins team (fx+ (vector-ref wins team) 1)))
    
        (define (increment-score! team score)
          (vector-set! scores team (fx+ (vector-ref scores team) score)))
    
        (define (reset-all-scores teams)
          (do ((i 0 (fx+ i 1))
    	   (teams teams (cdr teams)))
    	  ((fx= i 8) teams)
    	(vector-set! scores (car teams) 0)
    	(set-car! teams i)))
    
        (define (swap-teams! teams)
          (let ((temp (car teams)))
    	(set-car! teams (cadr teams))
    	(set-car! (cdr teams) temp)))
        
        (set-cdr! (last-pair teams) teams)
        
        (let loop ((k n)
    	       (teams teams)
    	       (points 0))
          (cond ((fxzero? k) ; simulation ends
    	     wins)
    	    ((fx<= 7 (team-score (car teams)))  ;; previous point winner wins game.
    	     (increment-wins! (car teams))
    	     (loop (fx- k 1) (reset-all-scores teams) 0))
                ((fxzero? (random-integer 2)) ; current winner wins point
    	     (increment-score! (car teams) (if (fx<= 7 points) 2 1))
    	     (swap-teams! teams)
    	     (loop k (cdr teams) (fx+ points 1)))
                (else ; current challenger wins point
    	     (increment-score! (cadr teams) (if (fx<= 7 points) 2 1))
    	     (loop k (cdr teams) (fx+ points 1)))))))
    
  4. Mike said

    In python:

    jai_alai_match() accepts a list of ratings to indicate the relative strengths of the players. The probablility of a player winning a point is the ratio of the players rating to the sum of both players ratings (see the threshold calculation below). The default is that each player is equally likely to win a point.

    from random import random
    
    ID  = 0
    RTG = 1
    PTS = 2
    
    def jai_alai_match(rating=[100]*8):
        queue = [[n,rating[n],0] for n in range(8)]
        pts = 0
        player1 = queue.pop(0)
        while player1[PTS] < 7:
            player2 = queue.pop(0)
            
            threshold = player1[RTG]/float(player1[RTG] + player2[RTG])
            if random() > threshold:
                player1,player2 = player2,player1
                
            player1[PTS] += (1 if pts<8 else 2)
            queue.append(player2)
    
            pts += 1
    
        return [player1] + queue
    
    def simulate(reps=10000,ratings=None):
        hist = [0]*8
        for n in range(reps):
            result = jai_alai_match(ratings) if ratings else jai_alai_match()
            winner = result[0]
            hist[winner[ID]] += 1
    
        for h in hist:
            print "{0:5.2}".format(h*100.0/reps),
        print
        
    #test
    simulate()
    simulate(ratings=[ 100, 100, 100, 100, 100, 100, 100, 160 ])
    
  5. slabounty said

    A Ruby implementation. There’s quite a few improvements to make, but I’m getting tired of looking at it ;-). This is quite a few more lines than the other implementations, but maybe someone will find it useful.

    I haven’t posted here before, but it looks like a great site.

    require 'getoptlong'
    
    class Team
        attr_reader :team_number
        attr_accessor :score
        attr_accessor :games_won
    
        def initialize(team_number)
            @score = 0
            @games_won = 0
            @team_number = team_number
        end
    
        def <=> (t)
            if @team_number < t.team_number 
                return -1
            elsif @team_number == t.team_number
                return 0
            else
                return 1
            end
        end
    end
    
    class Queue < Array
        alias :enqueue :<<
        alias :dequeue :shift
    end
    
    def sim_game(teams)
    
        # Sort the teams so they're in the initial starting order and
        # reset their scores to 0.
        teams.sort!
        teams.each { |t| t.score = 0 }
    
        winner = teams.dequeue
        total_points = 0
    
        while winner.score < 7 do
            challenger = teams.dequeue
            if rand <= 0.5 then
                winner.score += (total_points <= 7) ? 1 : 2
                teams.enqueue challenger
            else
                challenger.score += (total_points <= 7) ? 1 : 2
                teams.enqueue winner
                winner = challenger
            end
            total_points += 1
        end
    
        # Increment the winners game count.
        winner.games_won += 1
    
        # Put the winner back on the queue. Doesn't matter that it's at the end, as we'll resort later.
        teams.enqueue winner 
    end
    
    if __FILE__ == $PROGRAM_NAME
    
    # Set up the command line options
    opts = GetoptLong.new(
        ["--num_games", "-n", GetoptLong::REQUIRED_ARGUMENT],
        ["--verbose", "-v", GetoptLong::NO_ARGUMENT]
        )
    
    # Set the default values for the options
    num_games = 100
    $verbose = false
    
    # Parse the command line options. If we find one we don't recognize
    # an exception will be thrown and we'll rescue with a message.
    begin
        opts.each do | opt, arg|
            case opt
            when "--num_games"
                num_games = arg.to_i
            when "--verbose"
                $verbose = true
            end
        end
    rescue
        puts "Illegal command line option."
        exit
    end
    
    # Create the teams and initialize them.
    num_teams = 8
    teams = Queue.new
    1.upto(num_teams) do |t|
       teams.enqueue(Team.new(t))
    end
    
    # Run the simulation.
    1.upto(num_games) do
        sim_game(teams)
    end
    
    # Resort the teams and print out the winning percentages.
    teams.sort!
    teams.each do |t|
        puts "Team #{t.team_number} winning percentage is #{(t.games_won.to_f / num_games.to_f) * 100.0}"
    end
    
    end
    
    

Leave a comment