Skip to content

Integrating the new Design

Brenton Ashworth edited this page Jun 19, 2013 · 13 revisions

Integrating the new Design

Since receiving the new requirements, the designers have been hard at work on design and idea for the game. They have decided to render the Game with the excellent Raphael JavaScript library. The designers have handed over some JavaScript script code and we are going to work it into the design area of our project.

Adding a new page for game UI design

We will first add a new page to the Design area of our project where we can work on the UI for the game.

In the file tools/public/design.html add the following link

<li><a href="/design/game.html">Game</a></li>

and then add the file app/templates/game.html.

<_within file="application.html">

  <div id="content">

    <div class="row-fluid" template="tutorial" field="id:id">
      <div id="game-board"></div>
      <!-- We need this because of a bug in Pedestal. Remove when
      fixed. -->
      <div field="content:something"></div>
    </div>

  </div>

  <script id="script-driver" src="/game-driver.js"></script>

</_within>

The new UI will require raphael.js. Download this library and put it in app/assets/javascripts/. Next to this file create a JavaScript file named game.js. This file will contain all of our game rendering code. In the HTML file above, we refer to the game-driver.js file. Create this file next to game.js as well.

Finally, add the following scripts tags to application.html just before the <script id="script-driver"></script> tag at the bottom of the body section.

<script src="/raphael.js"></script>
<script src="/game.js"></script>

The game JavaScript

The designers on this project have created the UI for the game in JavaScript. This could also be written in ClojureScript. For this project, to emphasize the difference between rendering and application state, we will use the JavaScript for the UI.

The code for the game is shown below. We will add a brief comment for each section. All of this code should be go into the file game.js.

Creating a simple bar chart

// A multi-valued bar
// ================================================================================

var Bar = function(paper, x, y, vals) {

  var barAnimateTime = 2000;
  var barHeight = 20;

  var colors = ["#0f0", "#00f", "#f00"];

  var rect = function(x, y, w, h, color) {
    return paper.rect(x, y, w, h).attr({fill: color, stroke: "none"});
  }

  var bars = {};

  for(var i in vals) {
    var b = vals[i];
    var size = b.size || 0;
    b.bar = rect(x, y, size, barHeight, colors[i % colors.length]);
    bars[b.name] = b;
  }

  var resizeBar = function(bar, size) {
    bar.animate({width: size}, barAnimateTime);
  }

  return {
    setSize: function(name, n) {
      resizeBar(bars[name].bar, n);
    },
    vals: vals
  }
}
// A group of bars with unique value names
// ================================================================================

var Bars = function(bars) {

  var index = {};

  for(var i in bars) {
    var bar = bars[i];
    var vals = bar.vals;
    for(var j in vals) {
      var val = vals[j];
      index[val.name] = bar;
    }
  }

  return {
    setSize: function(name, n) {
      var b = index[name];
      if(b)
        b.setSize(name, n);
    }
  }
}

Drawing circles

// Circles
// ================================================================================

var Circles = function(paper, w, h) {

  var defaultRadius = 20;
  var padding = 50;
  var createAnimateTime = 500;
  var removeAnimateTime = 200;
  var moveAnimateTime = 1000;

  var reportScoreFn;
  var removeCounter = 0;

  var randomPoint = function() {
    var maxHeight = h - padding;
    var x = Math.floor(Math.random() * w);
    if(x < padding)
      x = padding;
    var y = Math.floor(Math.random() * h);
    if(y < padding)
      y = padding;
    if(y > maxHeight)
      y = maxHeight;
    return {x: x, y: y};
  }

  var removeCircle = function(c) {
    c.animate({r: 0}, removeAnimateTime, function() {
      c.remove();
    });
  }

  var moveCircle = function(c) {

    if(c) {
      var point = randomPoint();
      c.animate({"cx": point.x, "cy": point.y}, moveAnimateTime, function() {
        if(removeCounter > 0) {
          c.animate({fill: "#000"}, 100)
          removeCircle(c);
          removeCounter--;
        }
        moveCircle(c);
      });
    }
  }

  var makeCircle = function() {
    var point = randomPoint();
    var circle = paper.circle(point.x, point.y, 0).attr({
      fill: "#f00", stroke: "none", opacity: 0.6
    });
    circle.animate({r: defaultRadius}, createAnimateTime);
    moveCircle(circle);

    circle.mouseover(function() {
      if(reportScoreFn)
        reportScoreFn(1);
      removeCircle(circle);
    });
  }

  return {
    addCircle: function() {
      makeCircle();
    },
    removeCircle: function() {
      removeCounter++;
    },
    addScoreReporter: function(f) {
      reportScoreFn = f;
    }
  }
}

Showing a player's score

// Player
// ================================================================================

var Player = function(paper, x, y, name) {

  var nameLength = 150;
  var fontSize = 20;

  var score = 0;

  var nameText = paper.text(x, y, name).attr({
    "font-size": fontSize,
    "text-anchor": "start"});
  var scoreText = paper.text(x + nameLength, y, score).attr({
    "font-size": fontSize,
    "text-anchor": "end"});
  var st = paper.set();
  st.push(nameText, scoreText);

  return {
    setScore: function(n) {
      score = n;
      scoreText.attr({text: score});
    },
    moveTo: function(y) {
      st.animate({y: y}, 400);
    }
  }
}

The leaderboard

// Leaderboard
// ================================================================================

var Leaderboard = function(paper, x, y) {

  var playerSpacing = 30;

  var players = {};

  var playerY = function(i) {
    return 50 + (i * playerSpacing);
  }

  var countPlayers = function() {
    var count = 0;
    for(var i in players) {
      if(players.hasOwnProperty(i))
        count++;
    }
    return count;
  }

  return {
    addPlayer: function(name) {
      var i = countPlayers();
      var p = Player(paper, x, playerY(i), name);
      players[name] = p;
    },
    setScore: function(name, score) {
      var p = players[name];
      p.setScore(score);
    },
    setOrder: function(name, i) {
      var p = players[name];
      p.moveTo(playerY(i));
    },
    count: function() {
      return countPlayers();
    }
  }
}

The Bubble Game

// The Bubble Game
// ================================================================================

var BubbleGame = function(id) {

  var paper = Raphael(id, 800, 400);

  var bars = Bars([Bar(paper, 0, 380, [{name: "total-count"},
                                       {name: "max-count"},
                                       {name: "avg-count"}]),
                   Bar(paper, 0, 357, [{name: "max-dataflow"},
                                       {name: "avg-dataflow"},
                                       {name: "current-dataflow"}])]);

  var circles = Circles(paper, 500, 380);

  var leaderboard = Leaderboard(paper, 550, 0);

  // This will be removed as we make improvements to the game.
  // The dataflow will control when circles are created.
  var makeCircles = function() {
    var p = leaderboard.count();
    for(var i=0;i<p;i++) {
      circles.addCircle();
    }
  }
  setInterval(makeCircles, 2000);

  return {
    addHandler: circles.addScoreReporter,
    addPlayer: leaderboard.addPlayer,
    setScore: leaderboard.setScore,
    setOrder: leaderboard.setOrder,
    setStat: bars.setSize,
    addBubble: circles.addCircle,
    removeBubble: circles.removeCircle
  }
}

Playing nice with advanced compilation

// Game API
// ================================================================================

var createGame = function(id) {
  return BubbleGame(id);
}

var addPlayer = function(g, name) {
  g.addPlayer(name);
}

var addHandler = function(g, f) {
  g.addHandler(f);
}

var setScore = function(g, n, x) {
  g.setScore(n, x);
}

var addBubble = function(g) {
  g.addBubble();
}

var removeBubble = function(g) {
  g.removeBubble();
}

var setStat = function(g, n, v) {
  g.setStat(n, v);
}

var setOrder = function(g, n, i) {
  g.setOrder(n, i);
}

A JavaScript game Driver

var game = BubbleGame("game-board");
var me = {name: "Me", score: 0};
var players = [me,
               {name: "Fred", score: 0},
               {name: "ahbhgtre", score: 0}];

var sortPlayers = function() {
  players.sort(function(a, b) {
    if(a.score < b.score) return 1;
    if(a.score > b.score) return -1;
    return 0;
  });
  for(var i=0;i<players.length;i++) {
    players[i].newIndex = i;
  }
}

for(var i in players) {
  game.addPlayer(players[i].name);
}

var updateCounts = function() {
  var total = 0;
  var max = 0;
  for(var i in players) {
    var score = players[i].score;
    total += score;
    if(score > max)
      max = score;
  }
  var avg = total / players.length;

  game.setStat("total-count", total);
  game.setStat("max-count", max);
  game.setStat("avg-count", avg);
}
setInterval(updateCounts, 1000);

game.addHandler(function(points) {
  me.score += points;
  game.setScore("Me", me.score);
  updateCounts();
});

var rand = function(n) {
  return Math.floor(Math.random() * n) + n;
}

var randPlayer = function() {
  return Math.floor(Math.random() * players.length);
}

var updateDataflowStats = function() {
  game.setStat("max-dataflow", rand(100));
  game.setStat("avg-dataflow", rand(50));
  game.setStat("current-dataflow", rand(10));
}
setInterval(updateDataflowStats, 1000);

var updatePlayerScores = function() {
  var p = players[randPlayer()];
  if(p.name != "Me") {
    p.score += 1;
    game.setScore(p.name, p.score);
    game.removeBubble();
  }
  updateCounts();
}
setInterval(updatePlayerScores, 1000);

var updatePlayerOrder = function() {
  sortPlayers();
  for(var i in players) {
    var p = players[i];
    game.setOrder(p.name, i);
  }
}
setInterval(updatePlayerOrder, 2000);

Next steps

The tag for this step is step11.

Home | Rendering the Game

Clone this wiki locally