Skip to content

Commit

Permalink
feat: add pigeon (#1712)
Browse files Browse the repository at this point in the history
* add pigeon sounds

* add pigeon sound and image

* switch between animals on theme switch, rotate pigeon

* update pigeon sound and cow group type

* write pigeon test, address pigeon edge case

* change descriptions to be more general

* modify test slightly

* add line to test that I forgot

* add line to test to wait for element

* use xpath instead of css_selector

* get rid of unnecessary check

* fix typo

* test out adding delay to test

* check that cow is there

* fix typo

* test the test

* test if xpath works at all in this case

* switch back to using css_selector

* test out a small change to the test

* undo small change, run black

* Install latest portal

* address PR comments - dynamically change cows block

* continue work on dynamic pigeon block

* make pigeon block dynamic

* tidy up, add pigeon translations

---------

Co-authored-by: Florian Aucomte <[email protected]>
  • Loading branch information
evemartin and faucomte97 authored Sep 4, 2024
1 parent 4985e19 commit 5c7029b
Show file tree
Hide file tree
Showing 17 changed files with 1,525 additions and 37 deletions.
27 changes: 27 additions & 0 deletions game/end_to_end_tests/test_level_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,30 @@ def test_electric_fuel_gauge(self):
)
electric_fuel_gauge = self.selenium.find_element(By.ID, "electricFuelGauge")
assert "visibility: visible" in electric_fuel_gauge.get_attribute("style")

def test_pigeon(self):
"""Test that cows on the map automatically become pigeons when the theme is changed to "city" """
page = self.go_to_level_editor()
page.go_to_scenery_tab()

source_cow = self.selenium.find_element(By.ID, "cow")
end_space = self.selenium.find_element(
By.CSS_SELECTOR, "rect[x='130'][y='530']"
)
ActionChains(self.selenium).drag_and_drop(source_cow, end_space).perform()

scenery_cow = self.selenium.find_elements(
By.CSS_SELECTOR, "image[x='0'][y='0']"
)
cow_link = scenery_cow[0].get_attribute("href")
assert cow_link == "/static/game/raphael_image/Clarice.svg"

Select(self.selenium.find_element(By.ID, "theme_select")).select_by_value(
"city"
)

scenery_pigeon = self.selenium.find_elements(
By.CSS_SELECTOR, "image[x='0'][y='0']"
)
pigeon_link = scenery_pigeon[0].get_attribute("href")
assert pigeon_link == "/static/game/raphael_image/pigeon.svg"
684 changes: 684 additions & 0 deletions game/static/game/image/pigeon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions game/static/game/js/blockly/msg/js/ca.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ Blockly.Msg["TRAFFIC_LIGHT_GREEN_TITLE"] = "semàfor verd";
Blockly.Msg["DEAD_END_TITLE"] = "és carrer sense sortida";
Blockly.Msg["AT_DESTINATION_TITLE"] = "a destí";
Blockly.Msg["COW_CROSSING_TITLE"] = "vaques";
Blockly.Msg["PIGEON_CROSSING_TITLE"] = "coloms";

Blockly.Msg["CALL_PROC_TITLE"] = "Crida";
Blockly.Msg["CALL_PROC_TOOLTIP"] = "Crida un procediment";
Expand Down
1 change: 1 addition & 0 deletions game/static/game/js/blockly/msg/js/en-gb.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ Blockly.Msg["TRAFFIC_LIGHT_GREEN_TITLE"] = "traffic light green";
Blockly.Msg["DEAD_END_TITLE"] = "is dead end";
Blockly.Msg["AT_DESTINATION_TITLE"] = "at destination";
Blockly.Msg["COW_CROSSING_TITLE"] = "cows";
Blockly.Msg["PIGEON_CROSSING_TITLE"] = "pigeons";

Blockly.Msg["CALL_PROC_TITLE"] = "Call";
Blockly.Msg["CALL_PROC_TOOLTIP"] = "Calls a procedure";
Expand Down
1 change: 1 addition & 0 deletions game/static/game/js/blockly/msg/js/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ Blockly.Msg["TRAFFIC_LIGHT_GREEN_TITLE"] = "traffic light green";
Blockly.Msg["DEAD_END_TITLE"] = "is dead end";
Blockly.Msg["AT_DESTINATION_TITLE"] = "at destination";
Blockly.Msg["COW_CROSSING_TITLE"] = "cows";
Blockly.Msg["PIGEON_CROSSING_TITLE"] = "pigeons";

Blockly.Msg["CALL_PROC_TITLE"] = "Call";
Blockly.Msg["CALL_PROC_TOOLTIP"] = "Calls a procedure";
Expand Down
1 change: 1 addition & 0 deletions game/static/game/js/blockly/msg/js/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ Blockly.Msg["TRAFFIC_LIGHT_GREEN_TITLE"] = "semáforo en verde";
Blockly.Msg["DEAD_END_TITLE"] = "es un callejón sin salida";
Blockly.Msg["AT_DESTINATION_TITLE"] = "en destino";
Blockly.Msg["COW_CROSSING_TITLE"] = "vacas";
Blockly.Msg["PIGEON_CROSSING_TITLE"] = "palomas";

Blockly.Msg["CALL_PROC_TITLE"] = "Llamar";
Blockly.Msg["CALL_PROC_TOOLTIP"] = "Llamar a una función";
Expand Down
1 change: 1 addition & 0 deletions game/static/game/js/blockly/msg/js/fr.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ Blockly.Msg["TRAFFIC_LIGHT_GREEN_TITLE"] = "feu vert";
Blockly.Msg["DEAD_END_TITLE"] = "cul-de-sac";
Blockly.Msg["AT_DESTINATION_TITLE"] = "arrivé à destination";
Blockly.Msg["COW_CROSSING_TITLE"] = "vaches";
Blockly.Msg["PIGEON_CROSSING_TITLE"] = "pigeons";

Blockly.Msg["CALL_PROC_TITLE"] = "Appeler";
Blockly.Msg["CALL_PROC_TOOLTIP"] = "Appeler une fonction";
Expand Down
1 change: 1 addition & 0 deletions game/static/game/js/blockly/msg/js/hi.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ Blockly.Msg["TRAFFIC_LIGHT_GREEN_TITLE"] = "ट्रैफिक लाइट
Blockly.Msg["DEAD_END_TITLE"] = "आगे का रास्ता बंद है";
Blockly.Msg["AT_DESTINATION_TITLE"] = "मंजिल /गंतव्य पर";
Blockly.Msg["COW_CROSSING_TITLE"] = "गायों";
Blockly.Msg["PIGEON_CROSSING_TITLE"] = "कबूतरों";

Blockly.Msg["CALL_PROC_TITLE"] = "पुकारना";
Blockly.Msg["CALL_PROC_TOOLTIP"] = "एक प्रक्रिया बुलाना";
Expand Down
17 changes: 15 additions & 2 deletions game/static/game/js/blocklyCustomBlocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,13 +281,26 @@ function initCustomBlocksDescription() {
init: function() {
this.setColour(210);
this.setOutput(true, 'Boolean');
let imageUrl = ocargo.Drawing.animalType == ocargo.Cow.PIGEON ? ocargo.Drawing.pigeonUrl : ocargo.Drawing.whiteCowUrl
this.appendDummyInput()
.appendField(Blockly.Msg.COW_CROSSING_TITLE)
.appendField(new Blockly.FieldImage(ocargo.Drawing.imageDir + ocargo.Drawing.whiteCowUrl,
.appendField(ocargo.Drawing.animalType == ocargo.Cow.PIGEON ? Blockly.Msg.PIGEON_CROSSING_TITLE: Blockly.Msg.COW_CROSSING_TITLE)
.appendField(new Blockly.FieldImage(ocargo.Drawing.imageDir + imageUrl,
ocargo.BlocklyControl.COW_WIDTH,
ocargo.BlocklyControl.BLOCK_HEIGHT), 'IMAGE');
}
};

Blockly.Blocks['pigeon_crossing_IMAGE_ONLY'] = {
init: function() {
this.setColour(210);
this.setOutput(true, 'Boolean');
this.appendDummyInput()
.appendField("pigeons")
.appendField(new Blockly.FieldImage(ocargo.Drawing.imageDir + ocargo.Drawing.pigeonUrl,
ocargo.BlocklyControl.COW_WIDTH,
ocargo.BlocklyControl.BLOCK_HEIGHT), 'IMAGE');
}
}
/****************/
/* Procedures */
/****************/
Expand Down
7 changes: 4 additions & 3 deletions game/static/game/js/cow.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ ocargo.Cow.prototype.reset = function() {
ocargo.Cow.prototype.queueLeaveAnimation = function(model, node) {
ocargo.animation.appendAnimation({
type: 'callable',
functionCall: ocargo.sound.cow,
description: 'cow sound'
functionCall: this.type == ocargo.Cow.PIGEON ? ocargo.sound.pigeon : ocargo.sound.cow,
description: 'animal sound'
});
ocargo.animation.appendAnimation({
type: 'cow_leave',
id: this.id,
coordinate: node.coordinate,
description: 'Cow leaving'
description: 'animal leaving'
});
};

Expand All @@ -49,3 +49,4 @@ ocargo.Cow.ACTIVE = 'ACTIVE';
ocargo.Cow.INACTIVE = 'INACTIVE';
ocargo.Cow.WHITE = 'WHITE';
ocargo.Cow.BROWN = 'BROWN';
ocargo.Cow.PIGEON = 'PIGEON';
6 changes: 6 additions & 0 deletions game/static/game/js/drawing.js
Original file line number Diff line number Diff line change
Expand Up @@ -1236,11 +1236,14 @@ ocargo.Drawing.renderCoins = function (coins) {
}

ocargo.Drawing.cowUrl = function (type) {
ocargo.Drawing.animalType = type;
switch (type) {
case ocargo.Cow.WHITE:
return ocargo.Drawing.whiteCowUrl
case ocargo.Cow.BROWN:
return ocargo.Drawing.brownCowUrl
case ocargo.Cow.PIGEON:
return ocargo.Drawing.pigeonUrl
default:
return ocargo.Drawing.whiteCowUrl
}
Expand Down Expand Up @@ -1284,6 +1287,9 @@ ocargo.Drawing.TOP_VIEW = 'top_view'

ocargo.Drawing.whiteCowUrl = 'Clarice.svg'
ocargo.Drawing.brownCowUrl = 'Clarice_Jersey.svg'
ocargo.Drawing.pigeonUrl = 'pigeon.svg'

ocargo.Drawing.imageDir = '/static/game/image/'
ocargo.Drawing.raphaelImageDir = '/static/game/raphael_image/'

ocargo.Drawing.animalType = 'WHITE'
120 changes: 89 additions & 31 deletions game/static/game/js/level_editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,8 @@ ocargo.LevelEditor = function(levelId) {

for (var i = 0; i < BLOCKS.length; i++) {
var type = BLOCKS[i];
var block = Blockly.mainWorkspace.newBlock(type);
let usePigeons = type === "cow_crossing" && currentTheme == THEMES.city
var block = usePigeons ? Blockly.mainWorkspace.newBlock("pigeon_crossing_IMAGE_ONLY") : Blockly.mainWorkspace.newBlock(type);
block.initSvg();
block.render();

Expand Down Expand Up @@ -909,7 +910,7 @@ ocargo.LevelEditor = function(levelId) {
var color = COW_GROUP_COLOR_PALETTE[(currentCowGroupId - 1) % COW_GROUP_COLOR_PALETTE.length];
var style = 'background-color: ' + color;
var value = 'group' + currentCowGroupId++;
var type = ocargo.Cow.WHITE;
var type = currentTheme == THEMES.city ? ocargo.Cow.PIGEON : ocargo.Cow.WHITE;

cowGroups[value] = {
id: value,
Expand Down Expand Up @@ -2374,6 +2375,7 @@ ocargo.LevelEditor = function(levelId) {

function setTheme(theme) {
currentTheme = theme;
let newType = currentTheme == THEMES.city ? ocargo.Cow.PIGEON : ocargo.Cow.WHITE;

for (var x = 0; x < GRID_WIDTH; x++) {
for (var y = 0; y < GRID_HEIGHT; y++) {
Expand All @@ -2397,6 +2399,36 @@ ocargo.LevelEditor = function(levelId) {
});

$('#paper').css({'background-color': theme.background});

const animalSource = theme == THEMES.city ? "/static/game/image/pigeon.svg" : "/static/game/image/Clarice.svg";

$('#cow').each(function(index, element) {
element.src = animalSource;
})

$('#animals_label').each(function(index, element) {
element.innerHTML = theme == THEMES.city ? "Pigeons" : "Cows";
})

for (let [key, value] of Object.entries(cowGroups)) {
value["type"] = theme == THEMES.city ? ocargo.Cow.PIGEON : ocargo.Cow.WHITE;
}

for (let i = 0; i < cows.length; i++) {
cows[i].updateTheme();
}

const pigeonHTML = `<svg class="block_image"><g transform="translate(10,0)" <path="" class="blocklyPathDark" fill="#496684" d="m 0,0 H 111.34375 v 30 H 0 V 20 c 0,-10 -8,8 -8,-7.5 s 8,2.5 8,-7.5 z
"><path class="blocklyPath" stroke="none" fill="#5b80a5" d="m 0,0 H 111.34375 v 30 H 0 V 20 c 0,-10 -8,8 -8,-7.5 s 8,2.5 8,-7.5 z
"></path><path class="blocklyPathLight" stroke="#8ca6c0" d="m 0.5,0.5 H 110.84375 M 110.84375,0.5 M 0.5,29.5 V 18.5 m -7.36,-0.5 q -1.52,-5.5 0,-11 m 7.36,1 V 0.5 H 1
"></path><text class="blocklyText" y="12.5" transform="translate(10,5)">pigeons</text><g transform="translate(71.34375,5)"><image height="20px" width="30px" xlink:href="/static/game/image/pigeon.svg" alt=""></image></g></g></svg>`;

const cowHTML = `<svg class="block_image"><g transform="translate(10,0)" <path="" class="blocklyPathDark" fill="#496684" d="m 0,0 H 93.40625 v 30 H 0 V 20 c 0,-10 -8,8 -8,-7.5 s 8,2.5 8,-7.5 z
"><path class="blocklyPath" stroke="none" fill="#5b80a5" d="m 0,0 H 93.40625 v 30 H 0 V 20 c 0,-10 -8,8 -8,-7.5 s 8,2.5 8,-7.5 z
"></path><path class="blocklyPathLight" stroke="#8ca6c0" d="m 0.5,0.5 H 92.90625 M 92.90625,0.5 M 0.5,29.5 V 18.5 m -7.36,-0.5 q -1.52,-5.5 0,-11 m 7.36,1 V 0.5 H 1
"></path><text class="blocklyText" y="12.5" transform="translate(10,5)">cows</text><g transform="translate(53.40625,5)"><image height="20px" width="30px" xlink:href="/static/game/image/Clarice.svg" alt=""></image></g></g></svg>`;

$("#cow_crossing_image").html(newType == ocargo.Cow.PIGEON ? pigeonHTML : cowHTML);
}

function sortNodes(nodes) {
Expand Down Expand Up @@ -2554,35 +2586,6 @@ ocargo.LevelEditor = function(levelId) {
new InternalTrafficLight(trafficLightData[i]);
}

if(COW_LEVELS_ENABLED) {
var cowGroupData = JSON.parse(state.cows);
for (var i = 0; i < cowGroupData.length; i++) {
// Add new group to group select element
if (i >= Object.keys(cowGroups).length) {
addCowGroup();
}
var cowGroupId = Object.keys(cowGroups)[i];
cowGroups[cowGroupId].minCows = cowGroupData[i].minCows;
cowGroups[cowGroupId].maxCows = cowGroupData[i].maxCows;
cowGroups[cowGroupId].type = cowGroupData[i].type;

if (cowGroupData[i].potentialCoordinates != null) {
for (var j = 0; j < cowGroupData[i].potentialCoordinates.length; j++) {
var cowData = {
coordinates: [cowGroupData[i].potentialCoordinates[j]],
group: cowGroups[cowGroupId]
};
new InternalCow(cowData);
}
}
}

// Trigger change listener on cow group select box to set initial min/max values
$('#cow_group_select').change();

markCowNodes();
}

// Load in destination and origin nodes
if (state.destinations) {
var houses = JSON.parse(state.destinations);
Expand Down Expand Up @@ -2630,6 +2633,36 @@ ocargo.LevelEditor = function(levelId) {
PAPER_HEIGHT - currentTheme.decor[decor[i].decorName].height - decor[i].y + PAPER_PADDING);
}

//Load in cow data
if(COW_LEVELS_ENABLED) {
var cowGroupData = JSON.parse(state.cows);
for (var i = 0; i < cowGroupData.length; i++) {
// Add new group to group select element
if (i >= Object.keys(cowGroups).length) {
addCowGroup();
}
var cowGroupId = Object.keys(cowGroups)[i];
cowGroups[cowGroupId].minCows = cowGroupData[i].minCows;
cowGroups[cowGroupId].maxCows = cowGroupData[i].maxCows;
cowGroups[cowGroupId].type = cowGroupData[i].type;

if (cowGroupData[i].potentialCoordinates != null) {
for (var j = 0; j < cowGroupData[i].potentialCoordinates.length; j++) {
var cowData = {
coordinates: [cowGroupData[i].potentialCoordinates[j]],
group: cowGroups[cowGroupId]
};
new InternalCow(cowData);
}
}
}

// Trigger change listener on cow group select box to set initial min/max values
$('#cow_group_select').change();

markCowNodes();
}

// Load in block data
if(state.blocks) {
for(var i = 0; i < BLOCKS.length; i++) {
Expand Down Expand Up @@ -2918,6 +2951,31 @@ ocargo.LevelEditor = function(levelId) {

};

this.updateTheme = function() {
let newType = currentTheme == THEMES.city ? ocargo.Cow.PIGEON : ocargo.Cow.WHITE;
let transformDimensions = this["image"]["_"]["transform"][0]
let rotateDimensions = this["image"]["_"]["transform"][1]
let x = transformDimensions[1]
let y = transformDimensions[2]
let r = 0;
if (rotateDimensions) {
r = rotateDimensions[1];
}

this.image.remove();

this.image = drawing.createCowImage(newType);
if (this.controlledNode !== null) {
let controlledNode = this.controlledNode;
let coordinates = controlledNode.coordinate;
drawing.setCowImagePosition(coordinates, this.image, controlledNode);
} else {
this.image.transform("t" + x + "," + y + " r" + r);
}

setupCowListeners(this);
}

this.image = drawing.createCowImage(data.group.type);
this.valid = false;

Expand Down
8 changes: 8 additions & 0 deletions game/static/game/js/sound.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ ocargo.sound.cowSound = new Howl({
urls: ['/static/game/sound/cow.mp3', '/static/game/sound/cow.ogg']
});

ocargo.sound.pigeonSound = new Howl({
urls: ['/static/game/sound/pigeon.mp3', '/static/game/sound/pigeon.ogg']
});

function safePlay(sound) {
try {
sound.play();
Expand Down Expand Up @@ -96,6 +100,10 @@ ocargo.sound.cow = function() {
safePlay(ocargo.sound.cowSound);
};

ocargo.sound.pigeon = function() {
safePlay(ocargo.sound.pigeonSound);
}

ocargo.sound.sound_horn = function() {
safePlay(ocargo.sound.hornSound);
};
Expand Down
Loading

0 comments on commit 5c7029b

Please sign in to comment.