// ---- The Main script of the task ---- // This script use the Konva library to // use the canvas of HTML5 // ---- Define all elements var width = $("#form").innerWidth(); var height = $("#form").innerWidth(); // stage var stage = new Konva.Stage({ container: 'container', width: width, height: height, }); // layer var layer = new Konva.Layer(); // table plate var table_plate = new Konva.Circle({ x: stage.width() / 2, y: stage.height() / 2, radius: 70, fill: 'white', stroke: 'black', strokeWidth: 2, }); // food items var food_item_01 = new Konva.Circle({ x: 0, y: 0, radius: 7.5, fill: 'red', stroke: 'black', strokeWidth: 0, offset: { x: 0, y: -55 } }); var food_item_02 = food_item_01.clone(); var food_lot = new Konva.Group({ x: stage.width() / 2, y: stage.height() / 2, }); food_lot.add(food_item_01) // text for debug function writeMessage(message) { text.text(message); } var text = new Konva.Text({ x: 10, y: 10, fontFamily: 'Calibri', fontSize: 24, text: '', fill: 'black', }); // draw player seat var seat_edge = 30; var x01 = stage.width() / 2; var y01 = stage.height() / 2; var x02 = x01 - seat_edge / 2; var y02 = y01 + seat_edge / 2 * Math.sqrt(3); var x03 = x01 + seat_edge / 2; var y03 = y02; var seat = { points: [x01, y01, x02, y02, x03, y03], fill: 'white', stroke: 'white', strokeWidth: 2, closed: true, }; var player_seat = new Konva.Line(seat); var o1_seat = player_seat.clone(); var o2_seat = player_seat.clone(); // There's something wrong with the offset of polygons so we have to draw the seat by hand. var offset_radius = 75; player_seat.setAttr('offset', { x: 0, y: -offset_radius, }); o2_seat.setAttr('offset', { x: seat_edge / 2 + offset_radius * Math.sqrt(3) / 2, y: offset_radius / 2 + seat_edge * Math.sqrt(3) / 2 }); o1_seat.setAttr('offset', { x: -seat_edge / 2 - offset_radius * Math.sqrt(3) / 2, y: offset_radius / 2 + seat_edge * Math.sqrt(3) / 2 }); player_text = new Konva.Text({ x: stage.width() / 2, y: stage.height() / 2, fontFamily: 'Calibri', fontSize: 18, text: '', fill: 'black', }); o1_text = player_text.clone(); o1_text.setAttr('offset', { x: -seat_edge - offset_radius * Math.sqrt(3) / 2, y: offset_radius / 2 + seat_edge * Math.sqrt(3) / 2, }); o2_text = player_text.clone(); o2_text.setAttr('offset', { x: seat_edge + offset_radius * Math.sqrt(3) / 2 + 55, y: offset_radius / 2 + seat_edge * Math.sqrt(3) / 2, }); player_text.setAttr('offset', { x: 13, y: -offset_radius - 30, }); player_color = ['#DB4437','#F4B400','#4285F4'];//hex value, player 1-red, play 2-yellow, player 3-blue in google's logo // add the shape to the layer layer.add(table_plate) .add(text) .add(player_seat) .add(o1_seat) .add(o2_seat) .add(player_text) .add(o1_text) .add(o2_text) .add(food_lot); stage.add(layer); var send_button = new Konva.Label({ x: stage.width() / 2 - 25, y: stage.height() / 2, opacity: 0.75, visible: false, }); layer.add(send_button); send_button.add(new Konva.Tag({ fill: 'black', lineJoin: 'round', cornerRadius: 5, shadowColor: 'black', shadowBlur: 0, shadowOffset: 0, shadowOpacity: 0.75 })); send_button.add(new Konva.Text({ text: 'SEND', fontFamily: 'Calibri', fontSize: 18, padding: 5, fill: 'white' })); send_button.on('mouseout', function() { send_button.setAttr('opacity', 0.75); }) send_button.on('mouseover', function() { send_button.setAttr('opacity', 1); }) send_button.on('mousedown', function() { for (var food_angle of food_angle_list) { var index = agent_position_list.indexOf(food_angle); if (index === 0) { writeMessage('Please choose a player other than you!'); return; } if (index > 0) { table_plate.listening(false); send_button.hide(); send_button.listening(false); next_round_button.listening(true); var choice = index + my_id if (choice > 3) choice = choice - 3; writeMessage('You choose player'.concat(choice)); liveSend({ 'type': 'data', 'choice': choice, 'food_angle_list': food_angle_list }); return; } } writeMessage("You didn't choose anyone!"); }); // next round button var next_round_button = new Konva.Label({ x: stage.width() / 2 - 40, y: 4 * stage.height() / 5, opacity: 0.75, visible: false, }); layer.add(next_round_button); next_round_button.add(new Konva.Tag({ fill: 'black', lineJoin: 'round', cornerRadius: 5, shadowColor: 'black', shadowBlur: 0, shadowOffset: 0, shadowOpacity: 0.75 })); next_round_button.add(new Konva.Text({ text: 'Continue', fontFamily: 'Calibri', fontSize: 18, padding: 5, fill: 'white' })); next_round_button.on('mouseout', function() { next_round_button.setAttr('opacity', 0.75); }) next_round_button.on('mouseover', function() { next_round_button.setAttr('opacity', 1); }) next_round_button.on('mousedown', function() { liveSend({ 'type': 'control', 'msg': 'trial_finished' }); is_finished = true; next_round_button.hide(); writeMessage('Waiting for others to continue...'); }); // ---- Adjust elements and binding events to elements ---- // ---- Define target moving function is_movable(x, y) { var lower_len_bound = 20; // experiment on this. var vector_length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); return vector_length >= lower_len_bound; } function get_angle(x, y) { var angle = Math.atan(y / x) * 180 / Math.PI; if (Math.sign(x) === -1) { Math.sign(y) === 1 ? (angle += 180) : (angle -= 180); } return normalize_angle(angle); } function normalize_angle(angle) { while (Math.abs(angle) > 180) angle -= 360 * Math.sign(angle); if (angle === -180) angle = 180; return angle; } function get_rotate_angle(alpha, beta) { var angle = alpha - beta; angle = unit_arc_angle * Math.round(angle / unit_arc_angle); angle = normalize_angle(angle); // rotate to the positon by smallest angle return angle; } var reference_angle = -90; // the self postion for agent 1. var agent_position_list = [reference_angle, normalize_angle(reference_angle + 120), normalize_angle(reference_angle - 120) ]; // agent position counter-clock-wisely var lot_angle_list = [-90, 30, 150]; //lot angle list, in degree, 3 possible angles, counter-clock-wisely. var food_angle_list = [-90]; var food_angle_copy = [-90]; var unit_arc_angle = 120; // 360/3 = 120 degree var lot_index_table = [ // agent 1 as actor, decrease the using of hard code in the later version. [ [1], ], // agent 2 as actor [ [2], ], // agent 3 as actor [ [3] ], ]; // in the perspective of agent 1; table_plate.on('mousedown', function() { // rotate the table per the mouse position. var mouse_position = stage.getPointerPosition(); var x = mouse_position.x - stage.width() / 2; var y = mouse_position.y - stage.height() / 2; y = -y; // note the handness of the axis! var angle = 0; if (is_movable(x, y)) { angle_01 = get_rotate_angle(get_angle(x, y), food_angle_list[0]); angle_02 = get_rotate_angle(get_angle(x, y), food_angle_list[1]); angle = Math.abs(angle_01) >= Math.abs(angle_02) ? angle_02 : angle_01; } liveSend({ 'type': 'graphic', 'shape': 'angle', 'angle': angle }); rotate_table_by(angle); food_angle_list[0] = normalize_angle(food_angle_list[0] + angle); }); // ---- Define animation var rotation_time_limits = 0.5; // 0.5 seconds for rotating the table. var whole_angle = 180; var angular_speed = 0; var time_needed = 0; var rotate_angle = 0; var cum_angle = 0; var table_rotation = new Konva.Animation(function(frame) { // define our rotation animation var angular_diff = (frame.timeDiff * angular_speed) / 1000; if (frame.time / 1000 <= time_needed) { food_lot.rotate(angular_diff); cum_angle += angular_diff; } else { this.stop(); var angle_pad = rotate_angle - cum_angle; cum_angle = 0; food_lot.rotate(angle_pad); frame.time = 0; } }, layer); function rotate_table_by(angle) { rotate_angle = -angle; angular_speed = Math.sign(-angle) * whole_angle / 0.5; time_needed = rotation_time_limits * Math.abs(angle) / whole_angle; table_rotation.start(); } // ---- Define lvieRecv function for processing the data get from server var my_id = 0; var current_actor = 0; var current_position = 0; function liveRecv(data) { switch (data.msg_type) { case 'control': switch (data.msg) { case 'initialization': my_id = data.player_id; var o1_player = my_id + 1; if (o1_player > 3) { o1_player -= 3; } var o2_player = my_id + 2; if (o2_player > 3) { o2_player -= 3; } player_text.text('You'); o1_text.text('Player '.concat(o1_player.toString())); o2_text.text('Player '.concat(o2_player.toString())); player_seat.setAttr('fill',player_color[my_id-1]); o1_seat.setAttr('fill',player_color[o1_player-1]); o2_seat.setAttr('fill',player_color[o2_player-1]); //writeMessage('my id is'.concat(my_id.toString())); current_actor = data.next_actor; current_color = player_color[current_actor-1]; if (current_actor === my_id) { writeMessage('You are the actor, please choose'); send_button.show(); table_plate.listening(true); send_button.listening(true); } else { writeMessage('You are a receiver, waiting for the actor(Player ' .concat(current_actor.toString()) .concat(') to choose...') ); send_button.hide(); table_plate.listening(false); send_button.listening(false);; } var food_angle_indices = lot_index_table[current_actor - 1][0]; var food_angle_target = []; for (var idx of food_angle_indices) { food_angle_target.push(lot_angle_list[idx - 1]); } var food_item_list = [food_item_01]; food_item_01.setAttr('fill',current_color); for (var i in food_angle_list) { var rotate_angle = food_angle_target[i] - food_angle_list[i] - (my_id - 1) * 120; food_item_list[i].rotate(-rotate_angle); food_angle_list[i] = normalize_angle(food_angle_target[i] - (my_id - 1) * 120); } break; case 'all_players_finished': case 'task_ended': document.getElementById("form").submit(); break; } break; case 'data': switch (data.stage) { case 'send': if (my_id === current_actor) { writeMessage('The actor (YOU) chose Player '.concat(data.chosen_player.toString()).concat('. Please press the continue button to go to next round.')); } else { if (my_id === data.chosen_player) { writeMessage('The actor chose YOU (Player '.concat(data.chosen_player.toString()).concat('). Please press the continue button to go to next round.')); } else { writeMessage('The actor chose Player '.concat(data.chosen_player.toString()).concat('. Please press the continue button to go to next round.')); } } next_round_button.show(); next_round_button.listening(true); break; } break; case 'graphic': switch (data.stage) { case 'send': var angle = data.angle; rotate_table_by(angle); food_angle_list[0] = normalize_angle(food_angle_list[0] + angle); break; } break; } } layer.draw(); // ---- Run game_ready = 'Ready!'; window.addEventListener('DOMContentLoaded', (event) => { liveSend({ 'type': 'control', 'msg': 'page_loaded' }); });