// ---- 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, }); var my_id = 1; // in practice, each player are player 1 // 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(); player_seat.setAttr('fill','grey'); // There's something wrong with the offset of polygons so we have to draw the seat by hand. var offset_radius = 75; player_color = ['#DB4437','#F4B400','#4285F4'];//hex value, player 1-red, play 2-yellow, player 3-blue in google's logo player_seat.setAttr('offset', { x: 0, y: -offset_radius, }); player_seat.setAttr('fill',player_color[0]); o2_seat.setAttr('offset', { x: seat_edge / 2 + offset_radius * Math.sqrt(3) / 2, y: offset_radius / 2 + seat_edge * Math.sqrt(3) / 2 }); o2_seat.setAttr('fill',player_color[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 }); o1_seat.setAttr('fill',player_color[1]); food_item_01.setAttr('fill',player_color[0]); 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, }); o1_text.setAttr('text','Player 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, }); o2_text.setAttr('text','Player 3'); player_text.setAttr('offset', { x: 13, y: -offset_radius - 30, }); player_text.setAttr('text','You'); // 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: true, }); 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); }) // 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() { is_finished = true; next_round_button.hide(); var angle = food_angle_copy[0] - food_angle_list[0]; food_lot.rotate(-angle); table_plate.listening(true); send_button.listening(true); send_button.show(); writeMessage(''); food_angle_list = [-90]; }); // ---- Adjust elements and binding events to elements ---- 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); next_round_button.show(); var choice = index + my_id; if (choice > 3) choice = choice - 3; writeMessage('You choose player '.concat(choice) .concat('\nPress Continue to enter another pratice trial.') .concat('\nPress Next to begin the experiment!') ); return; } } writeMessage("You didn't choose anyone!"); }); 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':'practice'}) writeMessage('Great, now please press the SEND button to send the token'); rotate_table_by(angle); food_angle_list[0] = normalize_angle(food_angle_list[0] + angle); food_angle_list[1] = normalize_angle(food_angle_list[1] + angle); }); // ---- 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. 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 // ---- 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(); } function liveRecv(data) { switch (data.msg_type) { case 'control': switch (data.msg) { case 'practice': my_id = data.player_id; writeMessage(my_id); break; } } } // ---- Run game_ready = 'Ready!'; document.addEventListener("DOMContentLoaded", (event) => {});