OfflineAudioContext Latency Shift - web-audio-api

I'm using OfflineAudioContext to download an input file with effects applied. The download works great and it's really fast, but the problem I'm running into is that when I apply gain, I'm using an analyser to signal when the gain should increase or decrease.
This works great when playing the audio using an AudioContext, but the offline version causes the timing of the gain to shift very noticeably. The increase is starting late and the decrease is starting late. It's like there's a latency shift overall.
Is there a way to combat this shift? I'm fine with the rendering process taking longer.
var chunks = [];
var fileInput = document.getElementById("input");
var process = document.getElementById("process");
//Load audio file listener
process.addEventListener(
"click",
function () {
// Web Audio
var audioCtx2 = new (AudioContext || webkitAudioContext)();
// Reset buttons and log
$("#log").empty();
$("#download_link").addClass("d-none");
$("#repeat_link").addClass("d-none");
// Check for file
if (fileInput.files[0] == undefined) {
if ($("#upload_err").hasClass("d-none")) {
$("#upload_err").removeClass("d-none");
}
return false;
}
var reader1 = new FileReader();
reader1.onload = function (ev) {
// console.log("Reader loaded.");
var tempBuffer = audioCtx2.createBufferSource();
// Decode audio
audioCtx2.decodeAudioData(ev.target.result).then(function (buffer) {
// console.log("Duration1 = " + buffer.duration);
var offlineAudioCtx = new OfflineAudioContext({
numberOfChannels: 2,
length: 44100 * buffer.duration,
sampleRate: 44100,
});
// console.log("test 1");
// Audio Buffer Source
var soundSource = offlineAudioCtx.createBufferSource();
var analyser2d = offlineAudioCtx.createAnalyser();
var dgate1 = offlineAudioCtx.createGain();
var dhpf = offlineAudioCtx.createBiquadFilter();
var dhum60 = offlineAudioCtx.createBiquadFilter();
var dcompressor = offlineAudioCtx.createDynamicsCompressor();
dhpf.type = "highpass";
dhpf.Q.value = 0.5;
dhum60.type = "notch";
dhum60.Q.value = 130;
dcompressor.knee.setValueAtTime(40, offlineAudioCtx.currentTime);
dcompressor.attack.setValueAtTime(0.1, offlineAudioCtx.currentTime);
dcompressor.release.setValueAtTime(0.2, offlineAudioCtx.currentTime);
var reader2 = new FileReader();
// console.log("Created Reader");
reader2.onload = function (ev) {
// console.log("Reading audio data to buffer...");
$("#log").append("<p>Buffering...</p>");
soundSource.buffer = buffer;
let context = offlineAudioCtx;
//Before Effects
analyser2d = context.createAnalyser();
analyser2d.fftSize = 2048;
analyser2d.smoothingTimeConstant = 0.85;
const sampleBuffer = new Float32Array(analyser2d.fftSize);
function loop() {
analyser2d.getFloatTimeDomainData(sampleBuffer);
let sumOfSquares = 0;
for (let i = 0; i < sampleBuffer.length; i++) {
sumOfSquares += sampleBuffer[i] ** 2;
}
const avgPowerDecibels = Math.round(
10 * Math.log10(sumOfSquares / sampleBuffer.length)
);
const gainset = avgPowerDecibels > -50 ? 1 : 0;
//real-time effects choices start
if (
document.getElementById("gate").getAttribute("data-active") ===
"true"
) {
dgate1.gain.setTargetAtTime(
gainset,
offlineAudioCtx.currentTime,
0.05
);
} else if (
document.getElementById("gate").getAttribute("data-active") ===
"false"
) {
dgate1.gain.setTargetAtTime(
1,
offlineAudioCtx.currentTime,
0.05
);
}
if (
document.getElementById("hpf").getAttribute("data-active") ===
"true"
) {
dhpf.frequency.value = 90;
} else if (
document.getElementById("hpf").getAttribute("data-active") ===
"false"
) {
dhpf.frequency.value = 0;
}
if (
document.getElementById("hum").getAttribute("data-active") ===
"true"
) {
dhum60.frequency.value = 60;
} else if (
document.getElementById("hum").getAttribute("data-active") ===
"false"
) {
dhum60.frequency.value = 0;
}
if (
document.getElementById("comp").getAttribute("data-active") ===
"true"
) {
dcompressor.threshold.setValueAtTime(
-30,
offlineAudioCtx.currentTime
);
dcompressor.ratio.setValueAtTime(
3.5,
offlineAudioCtx.currentTime
);
} else if (
document.getElementById("comp").getAttribute("data-active") ===
"false"
) {
dcompressor.threshold.setValueAtTime(
0,
offlineAudioCtx.currentTime
);
dcompressor.ratio.setValueAtTime(1, offlineAudioCtx.currentTime);
}
// Display value.
requestAnimationFrame(loop);
}
loop();
soundSource
.connect(analyser2d)
.connect(dhpf)
.connect(dhum60)
.connect(dgate1)
.connect(dcompressor);
dcompressor.connect(offlineAudioCtx.destination);
offlineAudioCtx
.startRendering()
.then(function (renderedBuffer) {
// console.log('Rendering completed successfully.');
$("#log").append("<p>Rendering new file...</p>");
//var song = offlineAudioCtx.createBufferSource();
console.log(
"OfflineAudioContext.length = " + offlineAudioCtx.length
);
split(renderedBuffer, offlineAudioCtx.length);
$("#log").append("<p>Finished!</p>");
})
.catch(function (err) {
// console.log('Rendering failed: ' + err);
$("#log").append("<p>Rendering failed.</p>");
});
soundSource.loop = false;
};
reader2.readAsArrayBuffer(fileInput.files[0]);
soundSource.start(0);
});
};
reader1.readAsArrayBuffer(fileInput.files[0]);
},
false
);
I've included what I believe is the relevant portion of the code. Let me know if more is needed. Thanks!

This is doesn't work because the OfflineAudioContext runs faster (sometimes hundreds of times faster) than realtime. Your loop that gets the analyser data is done every 16 ms or so. In a realtime system, this might be accurate enough, but the offline context could be running much, much faster than realtime so by the time you've grabbed the analyser data, much more time has passed. (You can probably see this by printing out the context currentTime in the loop. It will probably increment by more (much more) than 16 ms.
The best way to do this is to use context.suspend(t) to suspend the context at known times so you can grab the analyser data synchronously. Note that the time is rounded so it might not be exactly the time you want but perhaps close enough. Note also that AFAIK, Firefox has not implemented this, and neither has Safari (but will soon).
Here's a short snippet with how do use suspend. There's more than one way though. Untested, but I think the general idea is correct:
// Grab the data every 16 ms, roughly. `suspend` rounds the time up to
// the nearest multiple of 128 frames (or 128/context.sampleRate time).
for (t = 0; t < <length of buffer>; t += 0.016) {
context.suspend(t)
.then(() => {
// Use analyser to grab the data and compute the values.
// Use setValueAtTime and friends to adjust the gain and compressor
// appropriately. Use context.currentTime to know at what time
// the context was suspended, and schedule the modifications to be
// at least 128 samples in the future. (I think).
})
.then(() => context.resume());
}
An alternative is to create a realtime context and process it as you do now, and when the buffer is finished playing, close the context. Of course, you'll have to add something (ScriptProcessorNode, AudioWorkletNode, or MediaRecorder) to capture the rendered data.
If none of these work for you, then I'm not sure what the alternatives would be.

Related

Call MapboxDirections() multiple times inside loop

I have been working with Mapbox Direction API and I am getting more than 100 of waypoints. As per Mapbox documentation I can't request more than 25 of WP to get a route, So I am calling it inside a loop of array with 25 WP chunk. Below is my sample code:
componentDidMount(waypoints) {
let wpChunks = this.chunkArray(waypoints, 25);
for (let chunk of wpChunks) {
routeArr.push(await this.callDirectionApi(chunk));
}
console.log('routeArr : ', routeArr);
}
callDirectionApi = (waypoints) => {
let directions;
directions = new MapboxDirections({
accessToken: mapboxgl.accessToken,
unit: 'metric',
profile: 'mapbox/driving',
interactive: false,
controls: false,
proximity: [88.30, 22.58]
});
this.map.on('load', (e) => {
let wpIndex = 0;
for (let ii = 0; ii <= waypoints.length-2; ii++) {
if (ii == 0) {
directions.setOrigin(waypoints[ii]);
} else {
directions.addWaypoint(wpIndex, waypoints[ii]);
wpIndex++;
}
}
directions.setDestination(waypoints[waypoints.length-1]);
directions.on('route', function(e) {
**console.log('Routes: ', e.route);**
resolve(e.route[0].geometry);
});
this.map.addControl(directions, 'top-left');
});
}
As a result, if I console.log the route output(the above bold line), I am getting same route as a response in each request inside loop.
Can anyone suggest me please am I doing anything wrong or not? Is this the right way to do so? If no, can you suggest any sample example?
Thanks
Probably the JS library doesn't work well with parallel requests. You should either adapt the code to make the requests one after another, or call the REST API directly.

How to remove L.rectangle(boxes[i])

I few days ago I implement a routingControl = L.Routing.control({...}) which works perfect for my needs. However I need for one of my customer also the RouteBoxer which I was also able to implement it. Now following my code I wants to remove the boxes from my map in order to draw new ones. However after 2 days trying to find a solution I've given up.
wideroad is a param that comes from a dropdown list 10,20,30 km etc.
function routeBoxer(wideroad) {
this.route = [];
this.waypoints = []; //Array for drawBoxes
this.wideroad = parseInt(wideroad); //Distance in km
this.routeArray = routingControl.getWaypoints();
for (var i=0; i<routeArray.length; i++) {
waypoints.push(routeArray[i].latLng.lng + ',' + routeArray[i].latLng.lat);
}
this.route = loadRoute(waypoints, this.drawRoute);
}; //End routeBoxer()
drawroute = function (route) {
route = new L.Polyline(L.PolylineUtil.decode(route)); // OSRM polyline decoding
boxes = L.RouteBoxer.box(route, this.wideroad);
var bounds = new L.LatLngBounds([]);
for (var i = 0; i < boxes.length; i++) {
**L.rectangle(boxes[i], {color: "#ff7800", weight: 1}).addTo(this.map);**
bounds.extend(boxes[i]);
}
console.log('drawRoute:',boxes);
this.map.fitBounds(bounds);
return route;
}; //End drawRoute()
loadRoute = function (waypoints) {
var url = '//router.project-osrm.org/route/v1/driving/';
var _this = this;
url += waypoints.join(';');
var jqxhr = $.ajax({
url: url,
data: {
overview: 'full',
steps: false,
//compression: false,
alternatives: false
},
dataType: 'json'
})
.done(function(data) {
_this.drawRoute(data.routes[0].geometry);
//console.log("loadRoute.done:",data);
})
.fail(function(data) {
//console.log("loadRoute.fail:",data);
});
}; //End loadRoute()
Well, my problem is now how to remove previously drawn boxes in order to draw new ones because of changing the wideroad using a dropdown list. Most of this code I got from the leaflet-routeboxer application.
Thanks in advance for your help...
You have to keep a reference to the rectangles so you can manipulate them (remove them) later. Note that neither Leaflet nor Leaflet-routeboxer will do this for you.
e.g.:
if (this._currentlyDisplayedRectangles) {
for (var i = 0; i < this._currentlyDisplayedRectangles.length; i++) {
this._currentlyDisplayedRectangles[i].remove();
}
} else {
this._currentlyDisplayedRectangles = [];
}
for (var i = 0; i < boxes.length; i++) {
var displayedRectangle = L.rectangle(boxes[i], {color: "#ff7800", weight: 1}).addTo(this.map);
bounds.extend(boxes[i]);
this._currentlyDisplayedRectangles.push(displayedRectangle);
}
If you don't store a reference to the L.rectangle() instance, you obviously won't be able to manipulate it later. This applies to other Leaflet layers as well - not storing explicit references to Leaflet layers is a usual pattern in Leaflet examples.

How to define cycles with observables

I'm trying to set up the update loop of a simple game, built with observables in mind. The top-level components are a model, which takes input commands, and produces updates; and a view, which displays the received updates, and produces input. In isolation, both work fine, the problematic part is putting the two together, since both depend on the other.
With the components being simplified to the following:
var view = function (updates) {
return Rx.Observable.fromArray([1,2,3]);
};
var model = function (inputs) {
return inputs.map(function (i) { return i * 10; });
};
The way I've hooked things together is this:
var inputBuffer = new Rx.Subject();
var updates = model(inputBuffer);
var inputs = view(updates);
updates.subscribe(
function (i) { console.log(i); },
function (e) { console.log("Error: " + e); },
function () { console.log("Completed"); }
);
inputs.subscribe(inputBuffer);
That is, I add a subject as a placeholder for the input stream, and attach the model to that. Then, after the view is constructed, I pass on the actual inputs to the placeholder subject, thus closing the loop.
I can't help but feel this is not the proper way to do things, however. Using a subject for this seems to be overkill. Is there a way to do the same thing with publish() or defer() or something along those lines?
UPDATE: Here's a less abstract example to illustrate what I'm having problems with. Below you see the code for a simple "game", where the player needs to click on a target to hit it. The target can either appear on the left or on the right, and whenever it is hit, it switches to the other side. Seems simple enough, but I still have the feeling I'm missing something...
//-- Helper methods and whatnot
// Variables to easily represent the two states of the target
var left = 'left';
var right = 'right';
// Transition from one side to the other
var flip = function (side) {
if (side === left) {
return right;
} else {
return left;
}
};
// Creates a predicate used for hit testing in the view
var nearby = function (target, radius) {
return function (position) {
var min = target - radius;
var max = target + radius;
return position >= min && position <= max;
};
};
// Same as Observable.prototype.scan, but it also yields the initial value immediately.
var initScan = function (values, init, updater) {
var initValue = Rx.Observable.return(init);
var restValues = values.scan(init, updater);
return initValue.concat(restValues);
};
//-- Part 1: From input to state --
var process = function (inputs) {
// Determine new state based on current state and input
var update = function(current, input) {
// Input value ignored here because there's only one possible state transition
return flip(current);
};
return initScan(inputs, left, update);
};
//-- Part 2: From display to inputs --
var display = function (states) {
// Simulate clicks from the user at various positions (only one dimension, for simplicity)
var clicks = Rx.Observable.interval(800)
.map(function (v) {return (v * 5) % 30; })
.do(function (v) { console.log("Shooting at: " + v)})
.publish();
clicks.connect();
// Display position of target depending on the model
var targetPos = states.map(function (state) {
return state === left ? 5 : 25;
});
// Determine which clicks are hits based on displayed position
return targetPos.flatMapLatest(function (target) {
return clicks
.filter(nearby(target, 10))
.map(function (pos) { return "HIT! (# "+ pos +")"; })
.do(console.log);
});
};
//-- Part 3: Putting the loop together
/**
* Creates the following feedback loop:
* - Commands are passed to the process function to generate updates.
* - Updates are passed to the display function to generates further commands.
* - (this closes the loop)
*/
var feedback = function (process, display) {
var inputBuffer = new Rx.Subject(),
updates = process(inputBuffer),
inputs = display(updates);
inputs.subscribe(inputBuffer);
};
feedback(process, display);
I think I understand what you are trying to achieve here:
How can I get a sequence of input events going in one direction that feed into a model
But have a sequence of output events going in the other direction that feed from the model to the view
I believe the answer here is that you probably want to flip your design. Assuming an MVVM style design, instead of having the Model know about the input sequence, it becomes agnostic. This means that you now have a model that has a InputRecieved/OnInput/ExecuteCommand method that the View will call with the input values. This should now be a lot easier for you to deal with a "Commands in one direction" and "Events in the other direction" pattern. A sort of tip-of-the-hat to CQRS here.
We use that style extensively on Views+Models in WPF/Silverlight/JS for the last 4 years.
Maybe something like this;
var model = function()
{
var self = this;
self.output = //Create observable sequence here
self.filter = function(input) {
//peform some command with input here
};
}
var viewModel = function (model) {
var self = this;
self.filterText = ko.observable('');
self.items = ko.observableArray();
self.filterText.subscribe(function(newFilterText) {
model.filter(newFilterText);
});
model.output.subscribe(item=>items.push(item));
};
update
Thanks for posting a full sample. It looks good. I like your new initScan operator, seems an obvious omission from Rx.
I took your code an restructured it the way I probably would have written it. I hope it help. The main things I did was encapsulted the logic into the model (flip, nearby etc) and have the view take the model as a parameter. Then I did also have to add some members to the model instead of it just being an observable sequence. This did however allow me to remove some extra logic from the view and put it in the model too (Hit logic)
//-- Helper methods and whatnot
// Same as Observable.prototype.scan, but it also yields the initial value immediately.
var initScan = function (values, init, updater) {
var initValue = Rx.Observable.return(init);
var restValues = values.scan(init, updater);
return initValue.concat(restValues);
};
//-- Part 1: From input to state --
var process = function () {
var self = this;
var shots = new Rx.Subject();
// Variables to easily represent the two states of the target
var left = 'left';
var right = 'right';
// Transition from one side to the other
var flip = function (side) {
if (side === left) {
return right;
} else {
return left;
}
};
// Determine new state based on current state and input
var update = function(current, input) {
// Input value ignored here because there's only one possible state transition
return flip(current);
};
// Creates a predicate used for hit testing in the view
var isNearby = function (target, radius) {
return function (position) {
var min = target - radius;
var max = target + radius;
return position >= min && position <= max;
};
};
self.shoot = function(input) {
shots.onNext(input);
};
self.positions = initScan(shots, left, update).map(function (state) {
return state === left ? 5 : 25;
});
self.hits = self.positions.flatMapLatest(function (target) {
return shots.filter(isNearby(target, 10));
});
};
//-- Part 2: From display to inputs --
var display = function (model) {
// Simulate clicks from the user at various positions (only one dimension, for simplicity)
var clicks = Rx.Observable.interval(800)
.map(function (v) {return (v * 5) % 30; })
.do(function (v) { console.log("Shooting at: " + v)})
.publish();
clicks.connect();
model.hits.subscribe(function(pos)=>{console.log("HIT! (# "+ pos +")");});
// Determine which clicks are hits based on displayed position
model.positions(function (target) {
return clicks
.subscribe(pos=>{
console.log("Shooting at " + pos + ")");
model.shoot(pos)
});
});
};
//-- Part 3: Putting the loop together
/**
* Creates the following feedback loop:
* - Commands are passed to the process function to generate updates.
* - Updates are passed to the display function to generates further commands.
* - (this closes the loop)
*/
var feedback = function (process, display) {
var model = process();
var view = display(model);
};
feedback(process, display);
I presume that because you do not "assign" the inputs after the model is created, you are aiming for a non-mutative approach to instantiating your model and view. However, your model and your view seem to depend on one another. To resolve this issue, you can use a third party to facilitate the relationship between the two objects. In this case, you can simply use a function for dependency injection...
var log = console.log.bind(console),
logError = console.log.bind(console, 'Error:'),
logCompleted = console.log.bind(console, 'Completed.'),
model(
function (updates) {
return view(updates);
}
)
.subscribe(
log,
logError,
logCompleted
);
By providing the model a factory to create a view, you give the model the ability to fully instantiate itself by instantiating it's view, but without knowing how the view is instantiated.
As per my comment on the question itself, here's the same sort of code you're writing done with a scheduler in Windows. I would expect a similar interface in RxJS.
var scheduler = new EventLoopScheduler();
var subscription = scheduler.Schedule(
new int[] { 1, 2, 3 },
TimeSpan.FromSeconds(1.0),
(xs, a) => a(
xs
.Do(x => Console.WriteLine(x))
.Select(x => x * 10)
.ToArray(),
TimeSpan.FromSeconds(1.0)));
The output I get, with three new numbers every second, is:
1
2
3
10
20
30
100
200
300
1000
2000
3000
10000
20000
30000

Streaming through SoundManager2 for webradios is pending for a very long time

I'm developping a little javascript snippet that allow a user to listen a webradio in streaming using soundManager2. By giving an URL to this snippet, the stream is sending me music so it's cool, it works.
Yes, but...
Query to the URL can be very very long (above 2-3 min!). I would like to know if there's a workaround or option I can use to make this query faster.
Why, opening my m3u (which just contains the mp3 URL inside) with Windows Media Player, the loading spend only 5-10 sec max, while acces to the same URL in a browser or with soundManager2 is during 2-3 min, sometimes more?
Here is my jsFiddle trying with the OuïFM (French radio). The waiting time, for this radio, is about 115 seconds.
var url = 'http://ice39.infomaniak.ch:8000/ouifm3.mp3';
var time = 0;
var timing = setInterval(function() {
$('#Streaming').html((time / 10) + ' seconds.');
time++;
}, 100);
var start = new Date;
soundManager.onready(function() {
soundManager.createSound({
id:'Radio',
url:url,
autoLoad: true,
autoPlay: true,
multiShot: false,
onload: function() {
$('#Loading').css('display', 'none');
clearInterval(timing);
}
});
});
Your fiddle was broken because SoundManager2's javascript wasn't loading properly. After I fixed that (and some of your code), I was able to get the audio playing in under a second: http://jsfiddle.net/y8GDp/
var url = 'http://ice39.infomaniak.ch:8000/ouifm3.mp3';
var time = 0;
var timer = $('#Streaming');
var loading = $('#Loading');
var start = new Date;
var seconds = function(){
var diff = ((new Date).getTime() - start.getTime()) / 1000;
return diff + ' seconds.';
};
var timing = setInterval(function() {
timer.html(seconds());
}, 100);
soundManager.onready(function() {
soundManager.createSound({
id:'Radio',
url:url,
autoPlay: true,
onplay: function() {
clearInterval(timing);
loading.html('Finished loading');
timer.html(seconds());
}
});
});

How to create a simulator and simulate real-time strategy game like Age of Empires Rise of Rome?

I would be interested to simulate an Age of Empires Rise of Rome gathering process of food, wood etc. Such simulator would help to acheive the economy growth and would find the optimized order to build buildings and gather recources. Note that the simulator would examine only economy process, not fighting. Is there any free software that would be easy to use and make such simulation? The question is how to simulate the process with simulator (not how to create real-time game).
Peladao asked for a example of a RTS engine. I am writting a example code here, and commenting it.
pseudocode:
loop:
"decide what want to build";
"try to build that"; (a verification is made if resources are enough)
"generate resources";
goto loop;
Real code (javascript)
1) not interesting. defining functions to use later.
var rand = function(){
return Math.random()*100;
};
var random = (function(){
return {
betwen:function(min,max){
return Math.random()*(max-min)+min;
},
element:function(datarray){
var len = datarray.length;
if(!len) return;
var r = parseInt(this.betwen(0,len),10);
return datarray[r];
}
};
})();
var scroll = (function(){
var index = 0;
var maximo = 10;
return {
cortarViejas:function(){
var edadCorte = index - maximo;
$(".logline").each(function(){
if($(this).data("nace")<edadCorte)
$(this).remove();
});
},
add:function(txt){
var line = $("<div>");
line.addClass("logline");
line.data("nace",index++);
line.html(txt);
$(line).insertAfter('#marca');
this.cortarViejas();
},
error:function(txt){
this.add("ERROR: "+txt);
},
battle:function(txt){
this.add("Battle: "+ txt);
},
oceaniaDice:function(txt){
this.add("[Oceania broadcast] "+ txt);
},
debugOceania:function(txt){
this.add("[Oceania debug] "+ txt);
}
};
})();
2) heres the interesting part. Oceania is a empire that generate resources, and invest these resources in tanks or soldiers
var oceania = (function(){
var tanks = 0;
var soldiers = 0;
var build = "";//build strategy
var dead = 0;
var prices = {"tank":5,"soldier":1};
var hateLines = [ "Eastasia will be destroyed","We hate Eurasia","Our troops are doing good",
"We have always ben at war with Eastasia","Under the spreading chestnut tree.I sold you and you sold me"];
var metal = 0;
var powerplants = 1;
var factory = 3;
return {
info:function(){
scroll.debugOceania("Build strategy:"+ build);
scroll.debugOceania("Metal:"+ metal);
scroll.debugOceania("Army: soldier "+soldiers + ", tanks "+tanks );
scroll.debugOceania("Dead:"+ dead);
},
militarTick:function(stuff){
if(tanks>10 && soldiers > 20){
tanks -= 10;
soldiers -= 20;
scroll.battle("Some units losts! 10 tanks, 20 soldiers");
dead += 10+ 20;
}
},
3) Here, the empire execute his desire to build [stuff]
buy:function(stuff){
if(!prices[stuff]) {
scroll.error("buying what?:"+stuff);
return;
}
if(prices[stuff]>metal) {
scroll.debugOceania(stuff + " is too expensive");
return;
}
metal -= prices[stuff];
switch(stuff){
case "tank":
tanks++;
//scroll.debugOceania("Oceania create tank");
break;
case "soldier":
soldiers++;
//scroll.debugOceania("Oceania create soldier");
break;
}
},
buildTick:function(){
switch(build){
case "tanks":
this.buy("tank");
break;
case "soldiers":
this.buy("soldier");
break;
}
},
3) Here, the empire decide what is his desire, based on tanks/soldiers ratio, ammount of soldiers, ammount of tanks, or any other strategy-level issue.
strategyTick:function(){
var likeSoldiers=0,likeTanks=0;
build = "nothing";
//You have something, build something!
if( metal> 10) //we have metal, lets buy soldiers!
likeSoldier = 10;
//minimum this
if ( tanks < 10)
likeTanks += 10;
if ( soldiers< 20)
likeSoldiers += 10;
//we want 5 soldiers for 1 tank
if ( soldiers < tanks * 5)
likeSoldiers += 10;
//if we have at least 40 soldiers, we would love to have more tanks!
if ( soldiers > 40)
likeTanks += 10;
//if we have at least 40 tanks, we would love to have moresoldiers!
if ( tanks > 40)
likeSoldiers += 10;
//we want soldiers more than anything else, lets build soldiers!
if(likeSoldiers > likeTanks ){
build = "soldiers";
}else
//we want tanks more than anything else, lets build soldiers!
if( likeTanks > 5) {
build = "tanks";
}
},
4) Everything after this line can be ignored.
produccionTick:function(){
var t;
var energy = powerplants * 3;
var factorycount = factory;
var metalInitial = metal;
for(t=0;t<energy,factorycount>0;t++,factorycount--){
metal = metal + 1;
}
//var produce = metal - metalInitial;
//scroll.debugOceania("Oceania create "+ produce + " metal. Total now:"+metal);
},
propagandaTick:function(){
if(rand()<5) {
var hate = random.element(hateLines);
scroll.oceaniaDice(hate);
}
},
tick:function(){
this.propagandaTick();
this.produccionTick();
this.strategyTick();
this.buildTick();
this.militarTick();
}
};
})();
$(function(){
$("#cmd").keyup(function(event){
if (event.which == 13) {
var command = $(this).val();
$(this).val("");
scroll.add(command);
$("#cmd").focus();
event.preventDefault();
}
});
$("#reload").click(function(){
document.location = "game.htm?r="+Math.random();
});
oceania.tick();
$(document).ready(function(){
setInterval(function(){
oceania.tick();
},300);
});
});