# Patrick Coleman # Comp Sys Res - 6th period - 2007-08 # Sugarscape - display require 'environment.rb' require 'agent.rb' require 'simulation.rb' require 'tk' #The step label $label = TkLabel.new(:text => "#{getStep}") #The drawing canvas $canvas #The graph $graph #A copy of the environment $env #GIF image of the environment at full capacity $image = TkPhotoImage.new(:file => 'env.gif') #Width of the canvas and graph $w #Number of time steps between refreshes of the display $refresh = 1 #True means display population graph and false means display wealth $porw = false #Combined play/pause button $pBtn #Refresh button that represents rate with arrows (>>>) $rbtn #Initializes all the widgets in the window def createGUI frame, env #Saves environment as variable $env = env #Sets width of canvas and graph $w = $env.env.length*10 #Creates frame for canvas and buttons TkFrame.new(frame).pack(:anchor=>:s,:padx=>'2m', :pady=>'2m') #Creates buttons and sets their command procedures to play, pause, and step the simulation and destroy the window qBtn=TkButton.new(frame, :command=>proc{quit}, :text=>'Quit').configure('underline'=>0) $pBtn=TkButton.new(frame, :command=>proc{play}, :text=>'Pause').configure('underline'=>0) sBtn=TkButton.new(frame, :command=>proc{step}, :text=>'Step').configure('underline'=>0) $rBtn=TkButton.new(frame, :command=>proc{newRefresh}, :text=>'Rate: >').configure('underline'=>0) gBtn=TkButton.new(frame, :command=>proc{changeGraph}, :text=>'Change graph').configure('underline'=>0) #Binds keys to the frame to mimic effects of buttons frame.bind("Any-Key-q") {quit} frame.bind("Any-Key-p") {play} frame.bind("Any-Key-s") {step} frame.bind("Any-Key-r") {newRefresh} frame.bind("Any-Key-c") {changeGraph} #Creates and packs the panes for the canvas and graph pane = TkPanedWindow.new(frame) label = TkLabelFrame.new(pane, :text=>'The Sugarscape') pane.add label glabel = TkLabelFrame.new(pane, :text=>'Graphical Data') pane.add glabel pane.pack(:fill=>:both,:expand=>:true) #Creates and packs the drawing canvas and graph $canvas = TkCanvas.new(label) { width $w; height $w; background 'white'; borderwidth 3} $canvas.pack(:fill=>:both,:expand=>:true) $graph = TkCanvas.new(glabel) { width $w; height $w; background 'white'; borderwidth 2} $graph.pack(:fill=>:both,:expand=>:true) #Packs the buttons qBtn.pack(:side=>:right) $pBtn.pack(:side=>:left) $rBtn.pack(:side=>:left) sBtn.pack(:side=>:left) $label.pack(:side=>:left) gBtn.pack(:side=>:right) #Adds the labeled frame with the canvas pane.add(label) #Draws the environment on the canvas drawEnv end #Cycles through the matrix of locations drawing them on the canvas def drawEnv #Resets the canvas $canvas.delete(:all)if getStep%$refresh == 0 #Draws the environment at full capacity as the background TkcImage.new($canvas, $w/2+3, $w/2+2, :image => $image)if getStep%$refresh == 0 if getStep%$refresh == 0 s = ((getStep-1)/100)%6 TkcRectangle.new($canvas,0,265,500,500,:fill=>'white',:outline=>'white') if s > 0 and s < 4 TkcRectangle.new($canvas,0,0,500,265,:fill=>'white',:outline=>'white') if s > 4 end #Cycles through the environment matrix for k in 0...$env.width do for j in 0...$env.height do loc = $env.env[k][j] #Draws a red circle in the location if it has an agent if loc.hasAgent != -1 and getStep%$refresh == 0 TkcOval.new($canvas,j*10+5, k*10+5, j*10+13, k*10+13,:fill=>'red',:outline=>'red') if loc.hasAgent == 0 TkcOval.new($canvas,j*10+5, k*10+5, j*10+13, k*10+13,:fill=>'blue',:outline=>'blue') if loc.hasAgent == 1 #Draws a yellow circle whose radius corresponds to the amount of sugar at locations with sugar #if the sugar quantity is at less than full capacity at the location elsif loc.changed and getStep%$refresh == 0 q = loc.sugarquant TkcRectangle.new($canvas,j*10+5,k*10+5,(j+1)*10+5,(k+1)*10+5,:fill=>'white',:outline=>'white') TkcOval.new($canvas,j*10+9-q,k*10+9-q,j*10+2*q+5,k*10+2*q+5, :fill=>'yellow',:outline=>'yellow') if q > 0 end #Resets each location in terms of having an agent loc.hasAgent= -1 #Regrows the sugar at each location loc.grow end end #Outputs the time step to the window $label.configure(:text => " Step: #{getStep}") end #Displays population size over time on the graph canvas def drawPop #Resets the graph $graph.delete(:all) #Draws titles and axes TkcLine.new($graph,40,40,40,$w-40) TkcLine.new($graph,40,$w-40,$w-40,$w-40) TkcText.new($graph,$w/2,30,:text=>"Population levels over time",:font=>['Helvetica',15,'bold']) TkcText.new($graph,10,$w/2,:text=>"P\no\np\n \nl\ne\nv\ne\nl",:font=>['Helvetica',10,'bold']) TkcText.new($graph,$w/2,$w-10,:text=>"Time",:font=>['Helvetica',10,'bold']) #Cycles through the population array connecting values with lines for n in 1...popLength do #Stops if there are less population values than the most that can be stored break if getPop[n] == nil TkcLine.new($graph,50+((n-1)*400/$popLength),$w-40-($w-100)*getPop[n-1]/maxPop,50+n*400/$popLength,$w-40-($w-100)*getPop[n]/maxPop) end #Adds an oval to the end of the line TkcOval.new($graph,50+n*400/$popLength-2,$w-40-($w-100)*getPop[-1]/maxPop-2,50+n*400/$popLength+2,$w-40-($w-100)*getPop[-1]/maxPop+2) #Adds values to the axes and at the end of the line tinit = getStep-getPop.length TkcText.new($graph,40,$w-30,:text=>"#{tinit}") TkcText.new($graph,50+n*400/$popLength,$w-30,:text=>"#{getStep}") TkcText.new($graph,25,60,:text=>"#{maxPop}") TkcText.new($graph,75+n*400/$popLength-2,$w-40-($w-100)*getPop[-1]/maxPop,:text=>"#{getPop[-1]}") TkcText.new($graph,25,$w-40,:text=>"0") end #Displays the Lorenz curve for wealth distribution on the graph canvas def drawWealth #Resets the graph $graph.delete(:all) #Draws titles and axes TkcLine.new($graph,40,40,40,$w-40,:arrow=>:first) TkcLine.new($graph,40,$w-40,$w-40,$w-40,:arrow=>:last) TkcText.new($graph,$w/2,30,:text=>"Wealth Distribution",:font=>['Helvetica',15,'bold']) TkcText.new($graph,10,$w/2,:text=>"%\n \no\nf\n \nW\ne\na\nl\nt\nh",:font=>['Helvetica',10,'bold']) TkcText.new($graph,$w/2,$w-10,:text=>"% of Population",:font=>['Helvetica',10,'bold']) #Creates local copies of the variables from the simulation wealths = getWealths totalW = getTotalW #Variables representing distance between points for the display x = 400.0/(wealths.length-1) y = 400.0/totalW #Variables representing distance between points to calculate the Gini coefficient dx = 1.0/(wealths.length-1) dy = 1.0/totalW #Gini coefficient variable which represents area between Lorenz curve and line of slope one giniAr = 0 #Cycles through array of wealths connecting wealth values by line and adding to the Gini area count = [0,0] for n in 1...wealths.length do count = [count[1],count[1]+wealths[n]] TkcLine.new($graph,40+(n-1)*x,$w-40-count[0]*y,40+n*x,$w-40-count[1]*y) giniAr += (count[0] + 0.5 * (count[1]-count[0]))*dx*dy end #Adds values to axes and displays calculated Gini coefficient TkcText.new($graph,30,$w-30,:text => "0%") TkcText.new($graph,440,$w-25,:text => "100% (#{$env.agents.length})") TkcText.new($graph,40,15,:text => "100%") TkcText.new($graph,40,30,:text => "(#{totalW})") TkcText.new($graph,150,150,:text=>"Gini coefficient:\n%f" % (1.0-2*giniAr),:font=>['Helvetica',10,'bold']) end #Redraws the canvas def updateDisp #Redraws the environment drawEnv #Redraws the graph if getStep%$refresh == 0 and $porw drawPop elsif getStep%$refresh == 0 drawWealth end end #Cycles through the preset values to only refresh the display after some number of steps def newRefresh #If the variable is not at its maximum it raises it by a power of ten if $refresh < 1000 $refresh *= 10 $rBtn.configure(:text=>"Rate: >>") if $refresh == 10 $rBtn.configure(:text=>"Rate: >>>") if $refresh == 100 $rBtn.configure(:text=>"Rate: >>>>") if $refresh == 1000 #Otherwise it sets it back to one else $refresh = 1 $rBtn.configure(:text=>"Rate: >") end end #Calls on the function to draw the opposite graph of what is currently displayed def changeGraph #Changes the variable to display this graph when the display is updated $porw = !$porw #Redraws the graph if $porw drawPop else drawWealth end end