From 0040d7df1ee1653fcfce08e80264f2062ae16ae7 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Thu, 21 Mar 2019 12:13:46 +0100 Subject: [PATCH 01/22] almost working for new version --- game_server_connection.js | 199 ------------------------- grid.js | 31 +++- index.html | 18 +-- infobar.js | 15 ++ infocontainer.js | 49 ++++++ local_match_manager.js | 69 +++++++++ main.js | 225 +++++++++++++++++++++++++++- online_match_manager.js | 199 +++++++++++++++++++++++++ settings.js | 2 +- sidebar.js | 184 ----------------------- style.css | 147 ++++++++++-------- websocket_connection.js | 303 ++++++++++++++++++++++++++++++++++++++ 12 files changed, 970 insertions(+), 471 deletions(-) delete mode 100644 game_server_connection.js create mode 100644 infobar.js create mode 100644 infocontainer.js create mode 100644 local_match_manager.js create mode 100644 online_match_manager.js delete mode 100644 sidebar.js create mode 100644 websocket_connection.js diff --git a/game_server_connection.js b/game_server_connection.js deleted file mode 100644 index 5ae101d..0000000 --- a/game_server_connection.js +++ /dev/null @@ -1,199 +0,0 @@ -class GameServerConnection -{ - constructor(ip, port) - { - this.ip = ip; - this.port = port; - this.player = null; - this.socket = null; - this.registered = false; - - this.game_manager = null; - this.connected = false; - } - - set_player(p) - { - this.player = p; - } - - set_game_manager(gm) - { - this.game_manager = gm; - } - - is_registered() - { - return this.registered; - } - - is_connected() - { - return this.connected; - } - - on_open(callback_func) - { - // TODO - console.log("connected to " + this.ip + ":" + this.port); - this.connected = true; - callback_func(); - } - - on_close() - { - this.game_manager.end_game_listener() - } - - on_error() - { - console.log("error in websocket connection"); - this.game_manager.connection_error_listener(); - } - - on_message(event) - { - var json_msg = event.data; - var msg = JSON.parse(json_msg); - - if (!msg.hasOwnProperty("type") || !msg.hasOwnProperty("data")) - { - console.log("received wrong formatted message"); - return; - } - - switch(msg.type) - { - case "register_response": - this.on_register_response(msg.data); - break; - - case "game_starts": - this.on_game_starts(msg.data); - break; - - case "move": - this.on_move(msg.data); - break; - - case "move_response": - this.on_move_response(msg.data); - break; - - case "end_game": - this.on_end_game(msg.data); - break; - } - - } - - on_register_response(data) - { - var success = data.success; - this.game_manager.register_response_listener(success); - - if (!success) - { - this.socket.close(); - } - } - - on_game_starts(data) - { - var op_name = data.opponent_name; - var is_first_move = data.is_first_move; - - this.game_manager.start_game_listener(op_name, is_first_move); - } - - on_move(data) - { - var sub_x = data.sub_x; - var sub_y = data.sub_y; - var x = data.x; - var y = data.y; - - this.game_manager.move_listener(sub_x, sub_y, x, y); - } - - on_move_response(data) - { - if (data.success) - { - console.log("move accepted"); - } - else - { - console.error("move not accepted"); - } - } - - on_end_game(data) - { - this.game_manager.end_game_listener(); - this.close(); - } - - connect(callback_func) - { - this.socket = new WebSocket("wss://" + this.ip + ":" + this.port); - this.socket.onmessage = (e => this.on_message(e)); - this.socket.onopen = (() => this.on_open(callback_func)); - this.socket.onerror = (() => this.on_error()); - this.socket.onclose = (() => this.on_close()); - } - - send_move(sub_x, sub_y, x, y) - { - var msg_object = { - type: "move", - data: { - sub_x: "" + sub_x, - sub_y: "" + sub_y, - x: "" + x, - y: "" + y - } - }; - - this.socket.send(JSON.stringify(msg_object)); - } - - register() - { - // register for game queue - var msg_object = { - type: "register", - data: { - id: this.player.get_id(), - name: this.player.get_name() - } - }; - - this.socket.send(JSON.stringify(msg_object)); - } - - send_disconnect() - { - if (!this.is_connected) - { - return; - } - var msg_object = { - type: "end_game", - data: { - msg: "" - } - }; - - this.socket.send(JSON.stringify(msg_object)); - } - - close() - { - if (this.is_connected) - { - this.is_connected = false; - this.socket.close(); - } - } -} \ No newline at end of file diff --git a/grid.js b/grid.js index 08f4b78..5b4127a 100644 --- a/grid.js +++ b/grid.js @@ -1,9 +1,9 @@ class Grid { - constructor(n, grid_container_div, tile_width, tile_height, ground_color) + constructor(n, parent, tile_width, tile_height, ground_color) { this.n = n; - this.grid_container_div = grid_container_div; + this.grid_container_div = null; this.tile_width = tile_width; this.tile_height = tile_height; this.ground_color = ground_color; @@ -11,6 +11,8 @@ class Grid this.won_player = null; this.n_complete_subgrids = 0; + this.parent = parent; + this.subgrids = [] console.log("create grid of size " + this.n); @@ -20,6 +22,10 @@ class Grid create() { + this.grid_container_div = document.createElement("div"); + this.grid_container_div.className = "grid-container"; + this.parent.appendChild(this.grid_container_div); + var x,y; for (y = 0; y < this.n; y++) { @@ -49,7 +55,10 @@ class Grid { this.check_win(sub_x, sub_y, x, y); this.check_complete(sub_x, sub_y, x, y); - this.click_callback(sub_x, sub_y, x, y); + if (this.click_callback != null) + { + this.click_callback(sub_x, sub_y, x, y); + } } player_change_listener(player) @@ -211,6 +220,22 @@ class Grid } } + unblock_all_non_completed() + { + var x,y; + for (y = 0; y < this.n; y++) + { + for (x = 0; x < this.n; x++) + { + if (this.subgrids[y][x].is_won() || this.subgrids[y][x].is_draw()) + { + continue; + } + this.subgrids[y][x].unblock(); + } + } + } + block(x,y) { this.subgrids[y][x].block(); diff --git a/index.html b/index.html index 6ab51da..758e041 100644 --- a/index.html +++ b/index.html @@ -8,23 +8,17 @@ ultimate tictactoe -
-
- -
+
- - - + + + + + diff --git a/infobar.js b/infobar.js new file mode 100644 index 0000000..1c6c2ce --- /dev/null +++ b/infobar.js @@ -0,0 +1,15 @@ +class Infobar +{ + constructor(parent) + { + this.parent = parent; + this.container = document.createElement("div"); + this.container.className = "infobar-container"; + this.parent.appendChild(this.container); + } + + create_infocontainer() + { + return new Infocontainer(this.container); + } +} \ No newline at end of file diff --git a/infocontainer.js b/infocontainer.js new file mode 100644 index 0000000..f72ffa4 --- /dev/null +++ b/infocontainer.js @@ -0,0 +1,49 @@ +class Infocontainer +{ + constructor(parent) + { + this.parent = parent; + this.container = document.createElement("div"); + this.container.className = "info-container"; + this.parent.appendChild(this.container); + } + + create_button(text) + { + var b = document.createElement("button"); + b.className = "infobar-button"; + b.appendChild(document.createTextNode(text)); + this.container.appendChild(b) + return b; + } + + create_input(placeholder, pw=false) + { + var i = document.createElement("input"); + i.className = "infobar-input"; + i.placeholder = placeholder; + i.type = pw ? "password" : "text"; + + this.container.appendChild(i); + return i; + } + + create_label(text) + { + var l = document.createElement("label"); + l.className = "infobar-label"; + l.innerHTML = text; + this.container.appendChild(l); + return l; + } + + hide() + { + this.container.style.display = "none"; + } + + show() + { + this.container.style.display = "inline-block"; + } +} \ No newline at end of file diff --git a/local_match_manager.js b/local_match_manager.js new file mode 100644 index 0000000..801b239 --- /dev/null +++ b/local_match_manager.js @@ -0,0 +1,69 @@ +class LocalMatchManager +{ + constructor(grid, status_label, control_container) + { + this.grid = grid; + this.status_label = status_label; + + this.control_container = control_container; + + this.control_container.show(); + + this.local_player_a = new Player("red player", 255,0,0); + this.local_player_b = new Player("green player", 0,255,0); + this.is_local_player_a = false; + + this.grid.register_click_callback((i,j,k,l) => this.click_listener(i,j,k,l)); + + this.grid.deactivate_all(); + this.grid.unblock_all(); + + + this.toggle_local_player(); + } + + click_listener(sub_x, sub_y, x,y) + { + // check whether the game is over: + if (grid.is_won()) + { + this.status_change_listener("" + grid.get_won_player().get_name() + " has won.", "Game Over") + this.end_game(); + } + else if (grid.is_complete()) + { + this.status_change_listener("Draw. Everybody looses!", "Game Over"); + this.end_game(); + } + else + { + this.toggle_local_player(); + this.grid.block_all() + if (this.grid.subgrids[y][x].is_draw() || this.grid.subgrids[y][x].is_won()) + { + this.grid.unblock_all_non_completed(); + } + else + { + this.grid.subgrids[y][x].unblock(); + } + } + } + + toggle_local_player() + { + this.is_local_player_a = !this.is_local_player_a; + var next_player = this.is_local_player_a ? this.local_player_a : this.local_player_b; + + this.status_label.innerHTML = "" + "it's " + next_player.get_name() + "'s turn..."; + this.grid.player_change_listener(next_player); + } + + end_game() + { + this.status_label.innerHTML = "Game Over. Game Closed"; + this.grid.block_all(); + } + + +} \ No newline at end of file diff --git a/main.js b/main.js index 55635ad..3dfe62c 100644 --- a/main.js +++ b/main.js @@ -3,16 +3,225 @@ var n = style.getPropertyValue("--tictactoe_n"); var tilesize = style.getPropertyValue("--tile-size"); var default_opacity = style.getPropertyValue("--opacity"); var ground_color = style.getPropertyValue("--ground-color"); +var main_container = document.getElementById("main-container"); -var create_game_container = document.getElementById("create-game-container"); -var setting_container = document.getElementById("setting-container"); -var control_container = document.getElementById("control-container"); -var info_container = document.getElementById("info-container"); -var grid = new Grid(n, document.getElementById("grid-container"), tilesize, tilesize, ground_color); -var server_connection = new GameServerConnection(server_url, server_port); -var game_manager = new GameManager(grid, server_connection); -var sidebar = new Sidebar(create_game_container, setting_container, control_container, info_container, game_manager); +var main_menu = new Infobar(main_container); +var grid = new Grid(n, main_container, tilesize, tilesize, ground_color); +var sub_menu = new Infobar(main_container); + + +// fill containers with buttons and containers: + +// empty dummy container on top (to force other containers to be at the bottom) +dummy_main = main_menu.create_infocontainer(); +dummy_sub = sub_menu.create_infocontainer(); + +// start container: +create_game_container = main_menu.create_infocontainer(); +create_game_container.create_label("Start Local Game"); +b_local_game = create_game_container.create_button("Local Game"); + +// register container: +register_container = main_menu.create_infocontainer(); +register_container.create_label("Register or login to play online"); +i_register_username = register_container.create_input("username"); +i_register_pw = register_container.create_input("password", true); +b_register = register_container.create_button("register/login"); + +// logout: +logout_container = sub_menu.create_infocontainer(); +l_username = logout_container.create_label("logged in as: "); +b_logout = logout_container.create_button("logout"); + + +// match control +match_control = sub_menu.create_infocontainer(); +b_end_game = match_control.create_button("End Game"); + +// fill subcontainer: +match_slot_container = sub_menu.create_infocontainer(); +match_slot_container.create_label("online matches"); + +// search match: +search_match_container = main_menu.create_infocontainer(); +search_match_container.create_label("Create Online Match"); +b_match_search = search_match_container.create_button("random match"); +search_match_container.create_label("

"); + +l_match_op = search_match_container.create_input("player name"); +b_match_invite = search_match_container.create_button("invite player"); + + + +//status: +status_container = main_menu.create_infocontainer(); +l_status_head = status_container.create_label("status:"); +l_status = status_container.create_label("..."); + +// global vars: + +game_manager = null; + +logged_in = false; + +// connection stuff: + +var connection = null; + + + + +// global funcs: + +disable_all_containers = function() +{ + create_game_container.hide(); + register_container.hide(); + logout_container.hide(); + match_slot_container.hide(); + search_match_container.hide(); + match_control.hide(); + status_container.hide(); + + dummy_main.hide(); + dummy_sub.hide(); +} + + +end_local_game = function() +{ + disable_all_containers(); + create_game_container.show(); + status_container.show(); + if (logged_in) + { + logout_container.show(); + search_match_container.show(); + match_slot_container.show(); + } + else + { + register_container.show(); + } + + game_manager = null; +} + +on_close_click = function() { + if (game_manager != null) + { + game_manager.end_game(); + end_local_game(); + } + else if (connection != null) + { + connection.on_close_click(); + } +} + + +start_local_game = function() +{ + console.log("clicked"); + disable_all_containers(); + match_control.show(); + dummy_main.show(); + dummy_sub.show(); + + status_container.show(); + + game_manager = new LocalMatchManager(grid, l_status, match_control, end_local_game); + +} + + +// connection stuff: + +login_callback = function() +{ + logged_in = true; + if (game_manager != null) + { + game_manager.end_game(); + + } + end_local_game(); + l_username.innerHTML = "logged in as: " + connection.player.get_name(); + +} + +login = function(){ + + if (connection != null) + { + connection.close(); + } + connection = new WebsocketConnection(server_url, server_port, grid, l_status, match_slot_container, match_control, login_callback); + connection.connect(i_register_username.value, i_register_pw.value); +} + +logout = function() +{ + logged_in = false; + if (connection != null) + { + + connection.close(); + + } + + connection = null; + grid.unblock_all(); + grid.deactivate_all(); + grid.block_all(); + end_local_game(); +} + +search_match = function() +{ + if (connection != null) + { + connection.send_match_request(null); + } +} + +invite_player = function() +{ + if (connection != null) + { + if (l_match_op.value == "") + { + l_status.innerHTML = "choose your opponent first!"; + } + else + { + connection.send_match_request(l_match_op.value); + } + } +} + +// initiate stuff and connect events: + +end_local_game(); + + +b_local_game.addEventListener("click", start_local_game); +b_end_game.addEventListener("click", on_close_click); +b_register.addEventListener("click", login); +b_logout.addEventListener("click", logout); + +b_match_search.addEventListener("click", search_match); +b_match_invite.addEventListener("click", invite_player); + + +/* +var websocket_connection = new WebsocketConnection('127.0.0.1', 5556); +websocket_connection.connect(); +*/ + + +// register resize event: window.addEventListener("resize", function() { var tilesize = getComputedStyle(document.body).getPropertyValue("--tile-size"); diff --git a/online_match_manager.js b/online_match_manager.js new file mode 100644 index 0000000..e32ef4a --- /dev/null +++ b/online_match_manager.js @@ -0,0 +1,199 @@ +class OnlineMatchManager +{ + constructor(grid, status_label, matches_container, control_container, game_server_connection, match_id, match_state, player_name) + { + this.grid = grid; + this.status_label = status_label; + + this.match_id = match_id; + + this.game_server_connection = game_server_connection; + + + this.match_state = match_state; + this.player_name = player_name + + var player_a = this.match_state.player_a; + var player_b = this.match_state.player_b; + + this.online_opponent = new Player(player_a == this.player_name ? player_b : player_a, 255,0,0); + this.local_player = new Player(this.player_name, 0,255,0); + + // create match button in match list: + this.control_container = control_container; + this.matches_container = matches_container + this.match_button = matches_container.create_button("" + this.online_opponent.get_name()); + this.match_button.addEventListener("click", () => this.open_match()); + this.match_button.style.background = "rbg(0,255,0)"; + + this.match_button.className = "infobar-button-active"; + + this.is_closed = false; + + } + + click_listener(sub_x, sub_y, x,y) + { + this.grid.block_all(); + + this.game_server_connection.send_move(sub_x, sub_y, x, y, this.match_id); + + this.status_label.innerHTML = "waiting for " + this.online_opponent.get_name() + "'s move"; + } + + on_user_close() + { + + // send end match message: + if (!this.match_state.game_over) + { + this.game_server_connection.send_end_match(this.match_id); + } + + this.matches_container.container.removeChild(this.match_button); + this.status_label.innerHTML = "match is closed"; + this.control_container.hide(); + this.is_closed = true; + + } + + remove_match() + { + if (!this.is_closed) + { + this.matches_container.container.removeChild(this.match_button); + } + } + + update_match_state(match_state) + { + this.match_state = match_state; + this.match_button.className = "infobar-button-active"; + + if (this.match_state.current_player != this.current_player_name) + { + this.game_server_connection.notify("" + this.online_opponent.get_name() + " moved"); + } + } + + open_match() + { + this.grid.register_click_callback(null); + + this.grid.deactivate_all(); + this.grid.unblock_all(); + + this.match_button.className = "infobar-button"; + + this.game_server_connection.set_active_match(this.match_id); + + this.control_container.show(); + + var complete_field = this.match_state.complete_field; + var global_field = this.match_state.global_field; + + var sub_x = null; + var sub_y = null; + var x = null; + var y = null; + + if (this.match_state.last_move != null) + { + sub_x = this.match_state.last_move.sub_x; + sub_y = this.match_state.last_move.sub_y; + x = this.match_state.last_move.x; + y = this.match_state.last_move.y; + } + + var game_over = this.match_state.game_over; + var player_won = this.match_state.player_won; + var current_player_name = this.match_state.active_player; + + var player_a = this.match_state.player_a; + var player_b = this.match_state.player_b; + + console.log(game_over); + console.log(current_player); + + var FIELD_EMPTY = 0 + var FIELD_USER = player_a == this.player_name ? 1 : 2; + var FIELD_OPPONENT = player_a == this.player_name ? 2 : 1; + var FIELD_DRAW = 3 + + this.online_opponent = new Player(player_a == this.player_name ? player_b : player_a, 255,0,0); + this.local_player = new Player(this.player_name, 0,255,0); + + var i,j; + for(j = 0; j < 9; j++) + { + for(i = 0; i < 9; i++) + { + var si = Math.floor(i / 3); + var sj = Math.floor(j / 3); + if (complete_field[j][i] == FIELD_USER) + { + this.grid.subgrids[sj][si].player_change_listener(this.local_player); + this.grid.subgrids[sj][si].cells[j % 3][i % 3].on_click(); + } + if (complete_field[j][i] == FIELD_OPPONENT) + { + this.grid.subgrids[sj][si].player_change_listener(this.online_opponent); + this.grid.subgrids[sj][si].cells[j % 3][i % 3].on_click(); + } + } + } + + this.grid.block_all(); + this.grid.register_click_callback((i,j,k,l) => this.click_listener(i,j,k,l)); + + if (game_over && player_won != null) + { + if (player_won == this.player_name) + { + this.status_label.innerHTML = "Congratulation, you won!"; + } + else + { + this.status_label.innerHTML = "Game over, you lost!"; + } + return; + } + else if(game_over) + { + this.status_label.innerHTML = "Game was closed by server or opponent"; + return; + } + + var current_player = this.player_name == current_player_name ? this.local_player : this.online_opponent; + this.grid.player_change_listener(current_player); + + + if (this.player_name == current_player_name) + { + + if (this.match_state.last_move != null) + { + if (this.grid.subgrids[y][x].is_won() || this.grid.subgrids[y][x].is_draw()) + { + this.grid.unblock_all_non_completed(); + } + else + { + this.grid.unblock(x,y); + } + this.status_label.innerHTML = "It's your turn!"; + } + else + { + this.grid.unblock_all(); + } + } + else + { + this.status_label.innerHTML = "waiting for " + this.online_opponent.get_name() + "'s move"; + } + + + + } +} \ No newline at end of file diff --git a/settings.js b/settings.js index 0c3c835..ee39b5d 100644 --- a/settings.js +++ b/settings.js @@ -1,2 +1,2 @@ var server_url = "the-cake-is-a-lie.net"; -var server_port = "5555"; \ No newline at end of file +var server_port = "5556"; \ No newline at end of file diff --git a/sidebar.js b/sidebar.js deleted file mode 100644 index d96cd08..0000000 --- a/sidebar.js +++ /dev/null @@ -1,184 +0,0 @@ -class Sidebar -{ - constructor(create_game_container, setting_container, control_container, info_container, game_manager) - { - this.create_game_container = create_game_container; - this.setting_container = setting_container; - this.control_container = control_container; - this.info_container = info_container; - - this.game_manager = game_manager; - - this.fill_containers(); - this.bind_events(); - - this.activate_create_game(); - this.activate_setting(); - this.activate_info(); - } - - create_button(text) - { - var b = document.createElement("button"); - b.className = "sidebar-button"; - b.appendChild(document.createTextNode(text)); - return b; - } - - create_input(text) - { - var i = document.createElement("input"); - i.className = "sidebar-input"; - i.type = "text"; - i.value = text; - return i; - } - - create_label(text) - { - var l = document.createElement("label"); - l.className = "sidebar-label"; - l.innerHTML = text; - return l; - } - - fill_containers() - { - - // create new game area: - this.create_game_container.appendChild(this.create_label("Choose game type")); - this.b_local = this.create_button("local game") - this.create_game_container.appendChild(this.b_local); - - this.b_remote = this.create_button("remote game"); - this.create_game_container.appendChild(this.b_remote); - - this.create_game_container.style.display = "none"; - - - // settings area - this.setting_container.appendChild(this.create_label("select online name:")); - - this.i_player_name = this.create_input(""); - this.setting_container.appendChild(this.i_player_name); - - this.setting_container.style.display = "none"; - - // control area: - - this.b_end_game = this.create_button("end game"); - this.control_container.appendChild(this.b_end_game); - - this.control_container.style.display = "none"; - - - // status area: - this.status_title = this.create_label(""); - this.info_container.appendChild(this.status_title); - - this.status_text = this.create_label("select gamemode.
here are the rules "); - this.info_container.appendChild(this.status_text); - - this.info_container.style.display = "none"; - - - - } - - bind_events() - { - // TODO - this.game_manager.register_game_mode_change_listener((c) => this.game_mode_change_listener(c)); - this.game_manager.register_status_change_listener((c,t=null) => this.status_change_listener(c, t)); - - this.b_local.addEventListener("click", () => this.game_manager.start_local_game()); - this.b_end_game.addEventListener("click", () => this.game_manager.end_game(true)); - this.b_remote.addEventListener("click", () => this.game_manager.register_remote_game(this.get_player_name())); - } - - set_status(text) - { - this.status_text.innerHTML = text; - } - - get_player_name() - { - return this.i_player_name.value; - } - - activate_create_game() - { - this.create_game_container.style.display = "inline-block"; - } - - activate_setting() - { - this.setting_container.style.display = "inline-block"; - } - - activate_control() - { - this.control_container.style.display = "inline-block"; - } - - activate_info() - { - this.info_container.style.display = "inline-block"; - } - - deactivate_create_game() - { - this.create_game_container.style.display = "none"; - } - - deactivate_setting() - { - this.setting_container.style.display = "none"; - } - - deactivate_control() - { - this.control_container.style.display = "none"; - } - - deactivate_info() - { - this.info_container.style.display = "none"; - } - - game_mode_change_listener(gamemode) - { - if (gamemode == "none") - { - this.activate_create_game(); - this.activate_setting(); - this.deactivate_control(); - this.activate_info(); - return - } - if (gamemode == "local") - { - this.deactivate_create_game(); - this.deactivate_setting(); - this.activate_control(); - this.activate_info(); - } - if (gamemode == "remote") - { - this.deactivate_create_game(); - this.deactivate_setting(); - this.activate_control(); - this.activate_info(); - } - } - - status_change_listener(statustext, title=null) - { - this.status_text.innerHTML = statustext; - if (title != null) - { - this.status_title.innerHTML = "

" + title + "

"; - } - } - -} \ No newline at end of file diff --git a/style.css b/style.css index 6200329..783a480 100644 --- a/style.css +++ b/style.css @@ -4,7 +4,7 @@ html, body { body { margin: 0; - background: rgb(30, 36, 119) ; + background: rgb(49, 54, 74) ; outline: 0; position: relative; } @@ -15,21 +15,36 @@ body { --tile-size: calc((100vh / (var(--tictactoe_n) * var(--tictactoe_n))) - 2 * var(--button-margin)); --opacity: 1; --border-radius: 1vh; - --sidebar-width: 25vh; - --ground-color: rgb(30, 36, 119) ; + --sidebar-width: 26vh; + --ground-color: rgb(41, 45, 62) ; --board-size: calc(var(--tictactoe_n) * var(--tictactoe_n) * var(--tile-size) + (var(--tictactoe_n) * var(--tictactoe_n) + var(--tictactoe_n)) * var(--button-margin) ); --sidebar-height: calc(var(--board-size) - 2 * var(--border-radius)); } /* override settings on portrait mode: */ -@media (orientation: portrait) { +@media (max-aspect-ratio: 16/10) { + + :root{ + --button-margin: 0.35vh; + --tile-size: calc(((70vh / (var(--tictactoe_n) * var(--tictactoe_n))) - 2 * var(--button-margin))); + --border-radius: 0.7vh; + --sidebar-width: 18.2vh; + --board-size: calc((var(--tictactoe_n) * var(--tictactoe_n) * var(--tile-size) + (var(--tictactoe_n) * var(--tictactoe_n) + var(--tictactoe_n)) * var(--button-margin))); + --sidebar-height: calc(var(--board-size) - 2 * var(--border-radius)); + } + +} + +/* override settings on portrait mode: */ +@media (max-aspect-ratio: 1/1) { :root{ --button-margin: 0.5vw; - --tile-size: calc((100vw / (var(--tictactoe_n) * var(--tictactoe_n))) - 2 * var(--button-margin)); + --tile-size: calc(((100vw / (var(--tictactoe_n) * var(--tictactoe_n))) - 2 * var(--button-margin))); --border-radius: 1vw; + --board-size: calc(var(--tictactoe_n) * var(--tictactoe_n) * var(--tile-size) + (var(--tictactoe_n) * var(--tictactoe_n) + var(--tictactoe_n)) * var(--button-margin) ); --sidebar-width: calc(var(--board-size) - 2 * var(--border-radius)); - --sidebar-height: 15vw; + --sidebar-height: 25vw; } } @@ -51,11 +66,11 @@ a:visited { } /* override settings on portrait mode: */ -@media (orientation: portrait) { +@media (max-aspect-ratio: 1/1) { .main-container { white-space: normal; - padding-top: calc(50vh - 0.5 * var(--board-size)); + padding-top: calc(50vh - 0.5 * var(--board-size) - var(--sidebar-height) - 2 * var(--button-margin)); } } @@ -114,11 +129,11 @@ a:visited { transition-duration: 0.5s; } -.sidebar-container { +.infobar-container { border-radius: var(--border-radius); padding: var(--border-radius); vertical-align: top; - background: rgba(255, 255, 255, 0.05); + background: none; display: inline-flex; justify-content: space-between; flex-direction:column; @@ -129,44 +144,15 @@ a:visited { } /* override settings on portrait mode: */ -@media (orientation: portrait) { +@media (max-aspect-ratio: 1/1) { - .sidebar-container { + .infobar-container { flex-direction:row; padding: none; } } -.create-game-container { - border-radius: var(--border-radius); - padding: var(--border-radius); - top: 0; - background: none; - display: inline-block; - vertical-align: middle; -} - - -.control-container { - border-radius: var(--border-radius); - padding: var(--border-radius); - top: 0; - background: none; - display: inline-block; - vertical-align: middle; -} - - -.setting-container { - border-radius: var(--border-radius); - padding: var(--border-radius); - top: 0; - background: none; - display: inline-block; - vertical-align: middle; -} - .info-container { border-radius: var(--border-radius); padding: var(--border-radius); @@ -178,7 +164,7 @@ a:visited { white-space: normal; } -.sidebar-label { +.infobar-label { color: white; display: flex; flex-direction: column; @@ -187,14 +173,14 @@ a:visited { text-align: left; } -@media (orientation: portrait) { - .sidebar-label +@media (max-aspect-ratio: 1/1) { + .infobar-label { - width: calc(var(--sidebar-width) * 0.3); + width: calc(var(--sidebar-width) * 0.25); } } -.sidebar-button { +.infobar-button { border-radius: var(--border-radius); margin: var(--border-radius); border: none; @@ -202,7 +188,7 @@ a:visited { transition-duration: 0.3s; background: rgba(0, 0, 0, 0.5); color: rgb(255, 255, 255); - font-size: calc(3 * var(--border-radius)); + font-size: calc(2.8 * var(--border-radius)); height: calc(4 * var(--border-radius)); width: calc(var(--sidebar-width) - 4 * var(--border-radius)); vertical-align: middle; @@ -210,30 +196,59 @@ a:visited { flex-direction: column; } -@media (orientation: portrait) { +@media (max-aspect-ratio: 1/1) { - .sidebar-button{ + .infobar-button{ width: calc(0.25 * var(--sidebar-width)); - height: calc(0.3 * var(--sidebar-height)); + height: calc(0.18 * var(--sidebar-height)); } } - -.sidebar-button:hover { - background: rgba(255, 255, 255, 0.2); -} - -.sidebar-button:active { - background: rgba(255, 255, 255, 0.4); -} - -.sidebar-input { +.infobar-button-active { border-radius: var(--border-radius); margin: var(--border-radius); border: none; outline: 0; transition-duration: 0.3s; - background: rgba(0, 0, 0, 0.5); + background: rgba(0, 255, 0, 0.5); + color: rgb(255, 255, 255); + font-size: calc(2.8 * var(--border-radius)); + height: calc(4 * var(--border-radius)); + width: calc(var(--sidebar-width) - 4 * var(--border-radius)); + vertical-align: middle; + display: flex; + flex-direction: column; +} + +@media (max-aspect-ratio: 1/1) { + + .infobar-button-active{ + width: calc(0.25 * var(--sidebar-width)); + height: calc(0.18 * var(--sidebar-height)); + } +} + + +.infobar-button:hover { + background: rgba(255, 255, 255, 0.2); +} + +.infobar-button:active { + background: rgba(255, 255, 255, 0.4); +} + +.infobar-button:disabled { + background: rgba(0, 0, 0, 0.1); + color: rgb(64,64,64); +} + +.infobar-input { + border-radius: var(--border-radius); + margin: var(--border-radius); + border: none; + outline: 0; + transition-duration: 0.3s; + background: rgba(0, 0, 0, 0.2); color: rgb(255, 255, 255); font-size: calc(3 * var(--border-radius)); height: calc(4 * var(--border-radius)); @@ -244,10 +259,14 @@ a:visited { text-align: center; } -@media (orientation: portrait) { +.infobar-input::placeholder { + color: rgba(255,255,255,0.3); +} - .sidebar-input{ - height: calc(0.3 * var(--sidebar-height)); +@media (max-aspect-ratio: 1/1) { + + .infobar-input{ + height: calc(0.18 * var(--sidebar-height)); width: calc(0.25 * var(--sidebar-width)); } } \ No newline at end of file diff --git a/websocket_connection.js b/websocket_connection.js new file mode 100644 index 0000000..d212c56 --- /dev/null +++ b/websocket_connection.js @@ -0,0 +1,303 @@ +class WebsocketConnection +{ + constructor(ip, port, grid, status_label, matches_container, control_container, login_callback_func) + { + this.ip = ip; + this.port = port; + this.session_id = null; + this.player = new Player("player", 128, 0,0); + this.socket = null; + this.registered = false; + + this.grid = grid; + this.status_label = status_label; + this.matches_container = matches_container; + this.control_container = control_container; + + this.active_match = null; + + this.connected = false; + + this.current_end_button = null; + + this.login_callback_func = login_callback_func; + + this.openmatches = {}; + + this. + + matches_container.hide(); + + + } + + set_player(p) + { + this.player = p; + } + + set_game_manager(gm) + { + this.game_manager = gm; + } + + is_registered() + { + return this.registered; + } + + is_connected() + { + return this.connected; + } + + on_open(username, pw) + { + this.connected = true; + this.login(username, pw) + } + + on_close() + { + this.registered = false; + this.connected = false; + } + + on_error() + { + console.log("error in websocket connection"); + this.registered = false; + this.connected = false; + } + + + on_message(event) + { + var json_msg = event.data; + var msg = JSON.parse(json_msg); + + if (!msg.hasOwnProperty("type") || !msg.hasOwnProperty("data")) + { + console.log("received wrong formatted message"); + return; + } + + console.log("raw_msg: " + json_msg); + + switch(msg.type) + { + case "login_response": + this.on_register_response(msg.data); + break; + + case "temp_session_response": + console.log(json_msg) + this.on_temp_session_response(msg.data); + break; + + case "match_update": + this.on_match_update(msg.data); + break; + + case "match_request_response": + this.on_match_request_response(msg.data); + break; + } + + } + + set_active_match(id) + { + this.active_match = id; + } + + on_close_click() + { + if (this.active_match != null && (this.active_match in this.openmatches)) + { + this.openmatches[this.active_match].on_user_close(); + + + } + } + + + on_match_update(data) + { + var id = data.id + var match_state = data.match_state; + + if (match_state == null) + { + // checking whether we can delete our dict object + if (id in this.openmatches) + { + delete this.openmatches[id]; + if (this.active_match == id) + { + if (this.current_end_button != null) + { + this.control_container.container.deleteChild(this.current_end_button); + this.current_end_button = null; + } + + } + } + } + else + { + if (id in this.openmatches) + { + this.openmatches[id].update_match_state(match_state) + console.log("update!!!"); + if (this.active_match == id) + { + this.openmatches[id].open_match(); + } + } + else + { + if (!match_state.game_over) + { + this.openmatches[id] = new OnlineMatchManager(this.grid, this.status_label, this.matches_container, this.control_container, this, id, match_state, this.player.get_name()); + } + + } + } + } + + + on_register_response(data) + { + if (data.success) + { + this.registered = true; + this.session_id = data.id; + this.login_callback_func(); + } + this.status_label.innerHTML = data.msg; + + } + + on_temp_session_response(data) + { + console.log("not supported yet"); + } + + on_match_request_response(data) + { + if (data.success) + { + this.status_label.innerHTML = "match request sent"; + } + else + { + this.status_label.innerHTML = "could not send request: " + data.msg; + } + } + + + connect(username, pw) + { + this.socket = new WebSocket("wss://" + this.ip + ":" + this.port); + this.socket.onmessage = (e => this.on_message(e)); + this.socket.onopen = (() => this.on_open(username, pw)); + this.socket.onerror = (() => this.on_error()); + this.socket.onclose = (() => this.on_close()); + } + + send_move(sub_x, sub_y, x, y, match_id) + { + var msg_object = { + type: "move", + data: { + id: match_id, + sub_x: "" + sub_x, + sub_y: "" + sub_y, + x: "" + x, + y: "" + y + } + }; + + this.socket.send(JSON.stringify(msg_object)); + } + + send_end_match(match_id) + { + var msg_object = { + type: "end_match", + data: { + id: match_id, + } + }; + + this.socket.send(JSON.stringify(msg_object)); + } + + send_match_request(player_name) + { + var msg_object = { + type: "match_request", + data: { + player: player_name + } + }; + this.socket.send(JSON.stringify(msg_object)); + } + + login(username, pw) + { + this.player.set_name(username); + // register for game queue + var msg_object = { + type: "login", + data: { + name: this.player.get_name(), + pw: pw + } + }; + + this.socket.send(JSON.stringify(msg_object)); + } + + send_disconnect() + { + if (!this.is_connected) + { + return; + } + var msg_object = { + type: "end_game", + data: { + msg: "" + } + }; + + this.socket.send(JSON.stringify(msg_object)); + } + + close() + { + for (var key in this.openmatches) + { + this.openmatches[key].remove_match(); + } + this.socket.close(); + } + + notify(text) { + + Notification.requestPermission(function(result) { + if (result === 'granted') { + navigator.serviceWorker.ready.then(function(registration) { + registration.showNotification(text, { + icon: './icon.png', + vibrate: [200, 200], + tag: "tictactoe-notification" + }); + }); + } + }); + + } +} From 05e5456c4b2c49fa5002dc356ea802fd77b3725d Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Thu, 21 Mar 2019 12:35:50 +0100 Subject: [PATCH 02/22] updated pwa --- manifest.json | 4 ++-- sw.js | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/manifest.json b/manifest.json index f0667a6..3133fc3 100644 --- a/manifest.json +++ b/manifest.json @@ -4,8 +4,8 @@ "display": "standalone", "start_url": "/website/ultimate_tictactoe/", "scope": "/website/ultimate_tictactoe/", - "theme_color": "#4650e2", - "background_color": "#1e2477", + "theme_color": "#7e57c2", + "background_color": "#31364A", "icons": [ { "src": "icon.png", diff --git a/sw.js b/sw.js index 54d2a7b..5791dd9 100644 --- a/sw.js +++ b/sw.js @@ -10,9 +10,11 @@ self.addEventListener('install', function(e) { '/website/ultimate_tictactoe/LICENSE', '/website/ultimate_tictactoe/main.js', '/website/ultimate_tictactoe/grid.js', - '/website/ultimate_tictactoe/game_manager.js', - '/website/ultimate_tictactoe/game_server_connection.js', - '/website/ultimate_tictactoe/sidebar.js', + '/website/ultimate_tictactoe/local_match_manager.js', + '/website/ultimate_tictactoe/online_match_manager.js', + '/website/ultimate_tictactoe/websocket_connection.js', + '/website/ultimate_tictactoe/infobar.js', + '/website/ultimate_tictactoe/infocontainer.js', '/website/ultimate_tictactoe/settings.js', '/website/ultimate_tictactoe/subgrid.js', '/website/ultimate_tictactoe/tile.js', From 7b4789dd2a06450892768081815e2b53097baba0 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Thu, 21 Mar 2019 14:15:00 +0100 Subject: [PATCH 03/22] more improvements --- main.js | 1 + online_match_manager.js | 9 +++++++-- websocket_connection.js | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/main.js b/main.js index 3dfe62c..6eae975 100644 --- a/main.js +++ b/main.js @@ -117,6 +117,7 @@ on_close_click = function() { else if (connection != null) { connection.on_close_click(); + match_control.hide(); } } diff --git a/online_match_manager.js b/online_match_manager.js index e32ef4a..d1f3c31 100644 --- a/online_match_manager.js +++ b/online_match_manager.js @@ -28,6 +28,11 @@ class OnlineMatchManager this.match_button.className = "infobar-button-active"; + if (this.match_state.active_player == this.local_player.get_name()) + { + this.game_server_connection.notify("your turn against " + this.online_opponent.get_name()); + } + this.is_closed = false; } @@ -70,9 +75,9 @@ class OnlineMatchManager this.match_state = match_state; this.match_button.className = "infobar-button-active"; - if (this.match_state.current_player != this.current_player_name) + if (this.match_state.active_player == this.local_player.get_name()) { - this.game_server_connection.notify("" + this.online_opponent.get_name() + " moved"); + this.game_server_connection.notify("your turn against " + this.online_opponent.get_name()); } } diff --git a/websocket_connection.js b/websocket_connection.js index d212c56..b5edc30 100644 --- a/websocket_connection.js +++ b/websocket_connection.js @@ -232,6 +232,7 @@ class WebsocketConnection }; this.socket.send(JSON.stringify(msg_object)); + } send_match_request(player_name) From 34c4e82330c70848d4eeb12598569fb0d84d39b3 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Thu, 21 Mar 2019 15:41:38 +0100 Subject: [PATCH 04/22] session cookies --- main.js | 81 ++++++++++++++++++++++++++++++++++++----- settings.js | 8 +++- sw.js | 24 ++++++++++++ websocket_connection.js | 74 ++++++++++++++++++++++++++++++++----- 4 files changed, 166 insertions(+), 21 deletions(-) diff --git a/main.js b/main.js index 6eae975..a303110 100644 --- a/main.js +++ b/main.js @@ -68,6 +68,39 @@ logged_in = false; // connection stuff: var connection = null; +var session_id = null; + + +// cookies: +function get_cookie(cname) { + var name = cname + "="; + var ca = document.cookie.split(';'); + for(var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1); + } + if (c.indexOf(name) == 0) { + return c.substring(name.length, c.length); + } + } + return ""; +} + +function set_cookie(cname, cvalue, exdays) { + var d = new Date(); + d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); + var expires = "expires="+d.toUTCString(); + document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; +} + +function check_cookie(cname) { + var tmp = get_cookie(cname); + if (tmp != "") { + return true; + } + return false; +} @@ -150,16 +183,8 @@ login_callback = function() end_local_game(); l_username.innerHTML = "logged in as: " + connection.player.get_name(); -} + set_cookie("sessionid", connection.session_id, 30); -login = function(){ - - if (connection != null) - { - connection.close(); - } - connection = new WebsocketConnection(server_url, server_port, grid, l_status, match_slot_container, match_control, login_callback); - connection.connect(i_register_username.value, i_register_pw.value); } logout = function() @@ -169,6 +194,12 @@ logout = function() { connection.close(); + if (check_cookie("sessionid")) + { + // delete session: + session_id = get_cookie("sessionid"); + set_cookie("sessionid", session_id, -100); + } } @@ -179,6 +210,36 @@ logout = function() end_local_game(); } +reconnect = function() +{ + if (check_cookie("sessionid")) + { + session_id = get_cookie("sessionid"); + if (connection != null) + { + connection.close(); + } + connection = new WebsocketConnection(server_url, server_port, grid, l_status, match_slot_container, match_control, login_callback, on_connection_error); + connection.reconnect(session_id); + } +} + +on_connection_error = function() +{ + connection = null; + logout(); +} + +login = function(){ + + if (connection != null) + { + connection.close(); + } + connection = new WebsocketConnection(server_url, server_port, grid, l_status, match_slot_container, match_control, login_callback, on_connection_error); + connection.connect(i_register_username.value, i_register_pw.value); +} + search_match = function() { if (connection != null) @@ -215,6 +276,8 @@ b_logout.addEventListener("click", logout); b_match_search.addEventListener("click", search_match); b_match_invite.addEventListener("click", invite_player); +reconnect(); + /* var websocket_connection = new WebsocketConnection('127.0.0.1', 5556); diff --git a/settings.js b/settings.js index ee39b5d..692cf89 100644 --- a/settings.js +++ b/settings.js @@ -1,2 +1,6 @@ -var server_url = "the-cake-is-a-lie.net"; -var server_port = "5556"; \ No newline at end of file +var server_protocol = "ws://" +var server_url = "127.0.0.1"; +var server_port = "5556"; + +var home = "https://the-cake-is-a-lie.net/website/ultimate_tictactoe/"; +var rel_home = "/website/ultimate_tictactoe"; \ No newline at end of file diff --git a/sw.js b/sw.js index 5791dd9..c19b768 100644 --- a/sw.js +++ b/sw.js @@ -32,4 +32,28 @@ self.addEventListener('install', function(e) { return response || fetch(event.request); }) ); +}); + +self.addEventListener('notificationclick', function(event) { + event.waitUntil(async function() { + const allClients = await clients.matchAll({ + includeUncontrolled: true + }); + + var instance = None; + + + // Let's see if we already have a window open: + for (const client of allClients) { + client.focus(); + instance = client; + break; + } + + // If we didn't find an existing window, + // open a new one: + if (!instance) { + instance = await clients.openWindow(rel_home); + } + }()); }); \ No newline at end of file diff --git a/websocket_connection.js b/websocket_connection.js index b5edc30..002a0e4 100644 --- a/websocket_connection.js +++ b/websocket_connection.js @@ -1,6 +1,6 @@ class WebsocketConnection { - constructor(ip, port, grid, status_label, matches_container, control_container, login_callback_func) + constructor(ip, port, grid, status_label, matches_container, control_container, login_callback_func, error_callback_func) { this.ip = ip; this.port = port; @@ -23,8 +23,9 @@ class WebsocketConnection this.login_callback_func = login_callback_func; this.openmatches = {}; + this.error_callback_func = error_callback_func; - this. + this.closed_by_user = false; matches_container.hide(); @@ -57,10 +58,22 @@ class WebsocketConnection this.login(username, pw) } + on_reopen(session_id) + { + this.connected = true; + this.relogin(session_id); + } + on_close() { this.registered = false; this.connected = false; + if (!this.closed_by_user) + { + this.status_label.innerHTML = "connection to server closed"; + this.error_callback_func(); + } + } on_error() @@ -68,6 +81,11 @@ class WebsocketConnection console.log("error in websocket connection"); this.registered = false; this.connected = false; + if (!this.closed_by_user) + { + this.status_label.innerHTML = "connection to server lost"; + this.error_callback_func(); + } } @@ -90,9 +108,8 @@ class WebsocketConnection this.on_register_response(msg.data); break; - case "temp_session_response": - console.log(json_msg) - this.on_temp_session_response(msg.data); + case "reconnect_response": + this.on_reconnect_response(msg.data); break; case "match_update": @@ -118,7 +135,13 @@ class WebsocketConnection this.openmatches[this.active_match].on_user_close(); - } + } + + if (this.current_end_button != null) + { + this.control_container.container.deleteChild(this.current_end_button); + this.current_end_button = null; + } } @@ -149,7 +172,6 @@ class WebsocketConnection if (id in this.openmatches) { this.openmatches[id].update_match_state(match_state) - console.log("update!!!"); if (this.active_match == id) { this.openmatches[id].open_match(); @@ -179,9 +201,17 @@ class WebsocketConnection } - on_temp_session_response(data) + on_reconnect_response(data) { - console.log("not supported yet"); + if (data.success) + { + this.registered = true; + this.session_id = data.id; + this.player.set_name(data.user); + this.login_callback_func(); + } + + this.status_label.innerHTML = data.msg; } on_match_request_response(data) @@ -199,13 +229,22 @@ class WebsocketConnection connect(username, pw) { - this.socket = new WebSocket("wss://" + this.ip + ":" + this.port); + this.socket = new WebSocket(server_protocol + this.ip + ":" + this.port); this.socket.onmessage = (e => this.on_message(e)); this.socket.onopen = (() => this.on_open(username, pw)); this.socket.onerror = (() => this.on_error()); this.socket.onclose = (() => this.on_close()); } + reconnect(session_id) + { + this.socket = new WebSocket(server_protocol + this.ip + ":" + this.port); + this.socket.onmessage = (e => this.on_message(e)); + this.socket.onopen = (() => this.on_reopen(session_id)); + this.socket.onerror = (() => this.on_error()); + this.socket.onclose = (() => this.on_close()); + } + send_move(sub_x, sub_y, x, y, match_id) { var msg_object = { @@ -261,6 +300,19 @@ class WebsocketConnection this.socket.send(JSON.stringify(msg_object)); } + relogin(session_id) + { + // register for game queue + var msg_object = { + type: "reconnect", + data: { + id: session_id + } + }; + + this.socket.send(JSON.stringify(msg_object)); + } + send_disconnect() { if (!this.is_connected) @@ -283,6 +335,8 @@ class WebsocketConnection { this.openmatches[key].remove_match(); } + this.status_label.innerHTML = "logged out"; + this.closed_by_user = true; this.socket.close(); } From 3634076c73830609bddec728fba00dff361cc1ab Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Thu, 21 Mar 2019 15:54:06 +0100 Subject: [PATCH 05/22] service worker fix --- sw.js | 89 +++++++++++++++++++++++------------------------------------ 1 file changed, 35 insertions(+), 54 deletions(-) diff --git a/sw.js b/sw.js index c19b768..8368e3c 100644 --- a/sw.js +++ b/sw.js @@ -1,59 +1,40 @@ -self.addEventListener('install', function(e) { - e.waitUntil( - caches.open('your-magic-cache').then(function(cache) { - return cache.addAll([ - '/website/ultimate_tictactoe/', - '/website/ultimate_tictactoe/index.html', - '/website/ultimate_tictactoe/manifest.json', - '/website/ultimate_tictactoe/icon.png', - '/website/ultimate_tictactoe/LICENSE', - '/website/ultimate_tictactoe/main.js', - '/website/ultimate_tictactoe/grid.js', - '/website/ultimate_tictactoe/local_match_manager.js', - '/website/ultimate_tictactoe/online_match_manager.js', - '/website/ultimate_tictactoe/websocket_connection.js', - '/website/ultimate_tictactoe/infobar.js', - '/website/ultimate_tictactoe/infocontainer.js', - '/website/ultimate_tictactoe/settings.js', - '/website/ultimate_tictactoe/subgrid.js', - '/website/ultimate_tictactoe/tile.js', - '/website/ultimate_tictactoe/README.md', - '/website/ultimate_tictactoe/site.js', - '/website/ultimate_tictactoe/style.css', - ]); - }) - ); - }); - - self.addEventListener('fetch', function(event) { - event.respondWith( - caches.match(event.request).then(function(response) { - return response || fetch(event.request); - }) - ); +self.addEventListener('install', function (e) { + e.waitUntil( + caches.open('your-magic-cache').then(function (cache) { + return cache.addAll([ + '/website/ultimate_tictactoe/', + '/website/ultimate_tictactoe/index.html', + '/website/ultimate_tictactoe/manifest.json', + '/website/ultimate_tictactoe/icon.png', + '/website/ultimate_tictactoe/LICENSE', + '/website/ultimate_tictactoe/main.js', + '/website/ultimate_tictactoe/grid.js', + '/website/ultimate_tictactoe/local_match_manager.js', + '/website/ultimate_tictactoe/online_match_manager.js', + '/website/ultimate_tictactoe/websocket_connection.js', + '/website/ultimate_tictactoe/infobar.js', + '/website/ultimate_tictactoe/infocontainer.js', + '/website/ultimate_tictactoe/settings.js', + '/website/ultimate_tictactoe/subgrid.js', + '/website/ultimate_tictactoe/tile.js', + '/website/ultimate_tictactoe/README.md', + '/website/ultimate_tictactoe/site.js', + '/website/ultimate_tictactoe/style.css', + ]); + }) + ); }); -self.addEventListener('notificationclick', function(event) { - event.waitUntil(async function() { - const allClients = await clients.matchAll({ - includeUncontrolled: true - }); +self.addEventListener('fetch', function (event) { + event.respondWith( + caches.match(event.request).then(function (response) { + return response || fetch(event.request); + }) + ); +}); - var instance = None; - - - // Let's see if we already have a window open: - for (const client of allClients) { - client.focus(); - instance = client; - break; - } - - // If we didn't find an existing window, - // open a new one: - if (!instance) { - instance = await clients.openWindow(rel_home); - } - }()); +self.addEventListener('notificationclick', function (event) { + event.notification.close(); + clients.openWindow(rel_home); }); \ No newline at end of file From f37cab9cc661e5876245a2ec008e77ff0887d36c Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Thu, 21 Mar 2019 15:55:00 +0100 Subject: [PATCH 06/22] settings inside service worker --- sw.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sw.js b/sw.js index 8368e3c..40a393e 100644 --- a/sw.js +++ b/sw.js @@ -1,4 +1,7 @@ +var home = "https://the-cake-is-a-lie.net/website/ultimate_tictactoe/"; +var rel_home = "/website/ultimate_tictactoe"; + self.addEventListener('install', function (e) { e.waitUntil( caches.open('your-magic-cache').then(function (cache) { From 5e19473e537184e4658cbc6c8cb96fe1cb721bed Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Thu, 21 Mar 2019 16:06:18 +0100 Subject: [PATCH 07/22] fixing notifications again --- online_match_manager.js | 5 ----- sw.js | 2 +- websocket_connection.js | 4 ++++ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/online_match_manager.js b/online_match_manager.js index d1f3c31..a9b5dc0 100644 --- a/online_match_manager.js +++ b/online_match_manager.js @@ -28,11 +28,6 @@ class OnlineMatchManager this.match_button.className = "infobar-button-active"; - if (this.match_state.active_player == this.local_player.get_name()) - { - this.game_server_connection.notify("your turn against " + this.online_opponent.get_name()); - } - this.is_closed = false; } diff --git a/sw.js b/sw.js index 40a393e..bfc5a46 100644 --- a/sw.js +++ b/sw.js @@ -39,5 +39,5 @@ self.addEventListener('fetch', function (event) { self.addEventListener('notificationclick', function (event) { event.notification.close(); - clients.openWindow(rel_home); + clients.openWindow(home); }); \ No newline at end of file diff --git a/websocket_connection.js b/websocket_connection.js index 002a0e4..0333062 100644 --- a/websocket_connection.js +++ b/websocket_connection.js @@ -182,6 +182,10 @@ class WebsocketConnection if (!match_state.game_over) { this.openmatches[id] = new OnlineMatchManager(this.grid, this.status_label, this.matches_container, this.control_container, this, id, match_state, this.player.get_name()); + if (match_state.last_move == null) + { + this.notify("new Game against " + this.openmatches[id].online_opponent.get_name()); + } } } From 93bec941f204d063e528bffca0332a89d0e2565d Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Thu, 21 Mar 2019 17:17:42 +0100 Subject: [PATCH 08/22] focus handling --- main.js | 20 ++++++++++++-------- websocket_connection.js | 5 +++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/main.js b/main.js index a303110..c69c32f 100644 --- a/main.js +++ b/main.js @@ -199,6 +199,7 @@ logout = function() // delete session: session_id = get_cookie("sessionid"); set_cookie("sessionid", session_id, -100); + session_id = null; } } @@ -278,16 +279,19 @@ b_match_invite.addEventListener("click", invite_player); reconnect(); - -/* -var websocket_connection = new WebsocketConnection('127.0.0.1', 5556); -websocket_connection.connect(); -*/ - - // register resize event: window.addEventListener("resize", function() { var tilesize = getComputedStyle(document.body).getPropertyValue("--tile-size"); grid.on_screen_orientation_change(tilesize, tilesize); -}) +}); + +window.onload = function() { + window.onfocus = function() { + if (session_id != null && connection == null) + { + reconnect(); + } + }; +}; + diff --git a/websocket_connection.js b/websocket_connection.js index 0333062..f67f6db 100644 --- a/websocket_connection.js +++ b/websocket_connection.js @@ -345,6 +345,11 @@ class WebsocketConnection } notify(text) { + + if (document.hasFocus()) + { + return; + } Notification.requestPermission(function(result) { if (result === 'granted') { From ba84d7f179ea7124496c97e94a5e691f03734fee Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Thu, 21 Mar 2019 17:30:24 +0100 Subject: [PATCH 09/22] link to rules --- main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.js b/main.js index c69c32f..2628e4d 100644 --- a/main.js +++ b/main.js @@ -56,8 +56,8 @@ b_match_invite = search_match_container.create_button("invite player"); //status: status_container = main_menu.create_infocontainer(); -l_status_head = status_container.create_label("status:"); -l_status = status_container.create_label("..."); +l_status_head = status_container.create_label("Status:"); +l_status = status_container.create_label("select gamemode.
here are the rules "); // global vars: From 1deec7790082d3a2802a32f28ddbf447ca013897 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Thu, 21 Mar 2019 20:03:09 +0100 Subject: [PATCH 10/22] bugfix in local game --- local_match_manager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/local_match_manager.js b/local_match_manager.js index 801b239..4bedaee 100644 --- a/local_match_manager.js +++ b/local_match_manager.js @@ -27,12 +27,12 @@ class LocalMatchManager // check whether the game is over: if (grid.is_won()) { - this.status_change_listener("" + grid.get_won_player().get_name() + " has won.", "Game Over") + this.status_label.innerHTML = "" + grid.get_won_player().get_name() + " has won."; this.end_game(); } else if (grid.is_complete()) { - this.status_change_listener("Draw. Everybody looses!", "Game Over"); + this.status_label.innerHTML = "Draw. Everybody looses!"; this.end_game(); } else From 02768a09711811b51f1bea0d243cdcc7a96ff2a9 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Thu, 21 Mar 2019 20:03:34 +0100 Subject: [PATCH 11/22] rule message --- main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.js b/main.js index 2628e4d..56fc021 100644 --- a/main.js +++ b/main.js @@ -57,7 +57,7 @@ b_match_invite = search_match_container.create_button("invite player"); //status: status_container = main_menu.create_infocontainer(); l_status_head = status_container.create_label("Status:"); -l_status = status_container.create_label("select gamemode.
here are the rules "); +l_status = status_container.create_label("select gamemode. click
[here] for the rules!"); // global vars: From 200a4c99b494092f23741c86cc1947bb0a0d60ce Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Thu, 21 Mar 2019 20:12:39 +0100 Subject: [PATCH 12/22] better messahe handling for failed logins --- websocket_connection.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/websocket_connection.js b/websocket_connection.js index f67f6db..13b9a72 100644 --- a/websocket_connection.js +++ b/websocket_connection.js @@ -66,9 +66,10 @@ class WebsocketConnection on_close() { + var login_failed = !this.registered; this.registered = false; this.connected = false; - if (!this.closed_by_user) + if (!this.closed_by_user && !login_failed) { this.status_label.innerHTML = "connection to server closed"; this.error_callback_func(); From a9429685ec52a5d0f67738bcd600f4cf21d4296c Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Fri, 22 Mar 2019 08:59:41 +0100 Subject: [PATCH 13/22] fixing errors --- grid.js | 2 +- local_match_manager.js | 9 ++++++--- websocket_connection.js | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/grid.js b/grid.js index 5b4127a..beda02e 100644 --- a/grid.js +++ b/grid.js @@ -90,7 +90,7 @@ class Grid check_complete(sub_x, sub_y, x, y) { - if (this.subgrids[sub_x][sub_y].is_won() || this.subgrids[sub_x][sub_y].is_draw()) + if (this.subgrids[sub_y][sub_x].is_won() || this.subgrids[sub_y][sub_x].is_draw()) { this.n_complete_subgrids++; } diff --git a/local_match_manager.js b/local_match_manager.js index 4bedaee..b4d2b81 100644 --- a/local_match_manager.js +++ b/local_match_manager.js @@ -33,7 +33,7 @@ class LocalMatchManager else if (grid.is_complete()) { this.status_label.innerHTML = "Draw. Everybody looses!"; - this.end_game(); + this.end_game(false); } else { @@ -59,9 +59,12 @@ class LocalMatchManager this.grid.player_change_listener(next_player); } - end_game() + end_game(closed_by_player = true) { - this.status_label.innerHTML = "Game Over. Game Closed"; + if (closed_by_player) + { + this.status_label.innerHTML = "Game Over. Game Closed"; + } this.grid.block_all(); } diff --git a/websocket_connection.js b/websocket_connection.js index 13b9a72..f581b18 100644 --- a/websocket_connection.js +++ b/websocket_connection.js @@ -64,7 +64,7 @@ class WebsocketConnection this.relogin(session_id); } - on_close() + on_close(login_failed=false) { var login_failed = !this.registered; this.registered = false; From 6650b9fc893740c861d62bb00fcb706e0c068ffb Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Fri, 22 Mar 2019 22:05:30 +0100 Subject: [PATCH 14/22] little improvements --- main.js | 5 ++-- online_match_manager.js | 42 ++++++++++++++++++++++++++++--- style.css | 56 ++++++++++++++++++++++++++++++++++++++--- websocket_connection.js | 13 ++++++++++ 4 files changed, 106 insertions(+), 10 deletions(-) diff --git a/main.js b/main.js index 56fc021..09c8b4d 100644 --- a/main.js +++ b/main.js @@ -37,11 +37,11 @@ b_logout = logout_container.create_button("logout"); // match control match_control = sub_menu.create_infocontainer(); -b_end_game = match_control.create_button("End Game"); +b_end_game = match_control.create_button("Close Match"); // fill subcontainer: match_slot_container = sub_menu.create_infocontainer(); -match_slot_container.create_label("online matches"); +match_slot_container.create_label("Running Matches
(click to open)"); // search match: search_match_container = main_menu.create_infocontainer(); @@ -59,6 +59,7 @@ status_container = main_menu.create_infocontainer(); l_status_head = status_container.create_label("Status:"); l_status = status_container.create_label("select gamemode. click
[here] for the rules!"); + // global vars: game_manager = null; diff --git a/online_match_manager.js b/online_match_manager.js index a9b5dc0..d80c93d 100644 --- a/online_match_manager.js +++ b/online_match_manager.js @@ -26,7 +26,14 @@ class OnlineMatchManager this.match_button.addEventListener("click", () => this.open_match()); this.match_button.style.background = "rbg(0,255,0)"; - this.match_button.className = "infobar-button-active"; + if (this.online_opponent.get_name() == this.match_state.active_player) + { + this.match_button.className = "infobar-button-red"; + } + else + { + this.match_button.className = "infobar-button-green" + } this.is_closed = false; @@ -68,7 +75,15 @@ class OnlineMatchManager update_match_state(match_state) { this.match_state = match_state; - this.match_button.className = "infobar-button-active"; + + if (this.online_opponent.get_name() == this.match_state.active_player) + { + this.match_button.className = "infobar-button-red"; + } + else + { + this.match_button.className = "infobar-button-green" + } if (this.match_state.active_player == this.local_player.get_name()) { @@ -76,6 +91,18 @@ class OnlineMatchManager } } + on_focus_loose() + { + if (this.online_opponent.get_name() == this.match_state.active_player) + { + this.match_button.className = "infobar-button-red"; + } + else + { + this.match_button.className = "infobar-button-green" + } + } + open_match() { this.grid.register_click_callback(null); @@ -83,7 +110,7 @@ class OnlineMatchManager this.grid.deactivate_all(); this.grid.unblock_all(); - this.match_button.className = "infobar-button"; + this.match_button.className = "infobar-button-active"; this.game_server_connection.set_active_match(this.match_id); @@ -160,7 +187,14 @@ class OnlineMatchManager } else if(game_over) { - this.status_label.innerHTML = "Game was closed by server or opponent"; + if (this.grid.is_complete()) + { + this.status_label.innerHTML = "Draw. Everyone looses!"; + } + else + { + this.status_label.innerHTML = "Game was closed by server or opponent"; + } return; } diff --git a/style.css b/style.css index 783a480..1a09803 100644 --- a/style.css +++ b/style.css @@ -170,7 +170,6 @@ a:visited { flex-direction: column; font-size: calc(2 * var(--border-radius)); width: calc(var(--sidebar-width) - 4 * var(--border-radius)); - text-align: left; } @media (max-aspect-ratio: 1/1) { @@ -194,6 +193,7 @@ a:visited { vertical-align: middle; display: flex; flex-direction: column; + text-align: center; } @media (max-aspect-ratio: 1/1) { @@ -204,13 +204,61 @@ a:visited { } } +.infobar-button-green { + border-radius: var(--border-radius); + margin: var(--border-radius); + border: none; + outline: 0; + transition-duration: 0.3s; + background: rgba(0, 255, 0, 0.2); + color: rgb(255, 255, 255); + font-size: calc(2.8 * var(--border-radius)); + height: calc(4 * var(--border-radius)); + width: calc(var(--sidebar-width) - 4 * var(--border-radius)); + vertical-align: middle; + display: flex; + flex-direction: column; +} + +@media (max-aspect-ratio: 1/1) { + + .infobar-button-green{ + width: calc(0.25 * var(--sidebar-width)); + height: calc(0.18 * var(--sidebar-height)); + } +} + +.infobar-button-red { + border-radius: var(--border-radius); + margin: var(--border-radius); + border: none; + outline: 0; + transition-duration: 0.3s; + background: rgba(255, 0, 0, 0.2); + color: rgb(255, 255, 255); + font-size: calc(2.8 * var(--border-radius)); + height: calc(4 * var(--border-radius)); + width: calc(var(--sidebar-width) - 4 * var(--border-radius)); + vertical-align: middle; + display: flex; + flex-direction: column; +} + +@media (max-aspect-ratio: 1/1) { + + .infobar-button-red{ + width: calc(0.25 * var(--sidebar-width)); + height: calc(0.18 * var(--sidebar-height)); + } +} + .infobar-button-active { border-radius: var(--border-radius); margin: var(--border-radius); border: none; outline: 0; transition-duration: 0.3s; - background: rgba(0, 255, 0, 0.5); + background: rgba(126,87,194, 0.8) ; color: rgb(255, 255, 255); font-size: calc(2.8 * var(--border-radius)); height: calc(4 * var(--border-radius)); @@ -230,11 +278,11 @@ a:visited { .infobar-button:hover { - background: rgba(255, 255, 255, 0.2); + background: rgba(126,87,194, 0.2); } .infobar-button:active { - background: rgba(255, 255, 255, 0.4); + background: rgba(126,87,194, 0.4); } .infobar-button:disabled { diff --git a/websocket_connection.js b/websocket_connection.js index f581b18..26f4dc7 100644 --- a/websocket_connection.js +++ b/websocket_connection.js @@ -126,6 +126,14 @@ class WebsocketConnection set_active_match(id) { + for (var key in this.openmatches) + { + if (key == id) + { + continue; + } + this.openmatches[key].on_focus_loose(); + } this.active_match = id; } @@ -243,6 +251,11 @@ class WebsocketConnection reconnect(session_id) { + for (var key in this.openmatches) + { + this.openmatches[key].remove_match(); + } + this.openmatches = {}; this.socket = new WebSocket(server_protocol + this.ip + ":" + this.port); this.socket.onmessage = (e => this.on_message(e)); this.socket.onopen = (() => this.on_reopen(session_id)); From 5f7b231d604de6f6389c9c4867e168ba88c3f404 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Sat, 23 Mar 2019 16:32:55 +0100 Subject: [PATCH 15/22] better theming --- index.html | 1 + main.js | 5 +++-- style.css | 10 +++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index 758e041..3d188ea 100644 --- a/index.html +++ b/index.html @@ -2,6 +2,7 @@ + diff --git a/main.js b/main.js index 09c8b4d..1165331 100644 --- a/main.js +++ b/main.js @@ -24,10 +24,11 @@ b_local_game = create_game_container.create_button("Local Game"); // register container: register_container = main_menu.create_infocontainer(); -register_container.create_label("Register or login to play online"); +register_container.create_label("Login to play online"); i_register_username = register_container.create_input("username"); i_register_pw = register_container.create_input("password", true); b_register = register_container.create_button("register/login"); +register_container.create_label("(creates new account for a new username)"); // logout: logout_container = sub_menu.create_infocontainer(); @@ -239,7 +240,7 @@ login = function(){ connection.close(); } connection = new WebsocketConnection(server_url, server_port, grid, l_status, match_slot_container, match_control, login_callback, on_connection_error); - connection.connect(i_register_username.value, i_register_pw.value); + connection.connect(i_register_username.value.toLowerCase(), i_register_pw.value); } search_match = function() diff --git a/style.css b/style.css index 1a09803..ee17b99 100644 --- a/style.css +++ b/style.css @@ -165,7 +165,7 @@ a:visited { } .infobar-label { - color: white; + color: rgba(224, 217, 235, 0.8); display: flex; flex-direction: column; font-size: calc(2 * var(--border-radius)); @@ -185,7 +185,7 @@ a:visited { border: none; outline: 0; transition-duration: 0.3s; - background: rgba(0, 0, 0, 0.5); + background: rgba(126,87,194, 0.2) ; color: rgb(255, 255, 255); font-size: calc(2.8 * var(--border-radius)); height: calc(4 * var(--border-radius)); @@ -278,11 +278,11 @@ a:visited { .infobar-button:hover { - background: rgba(126,87,194, 0.2); + background: rgba(126,87,194, 0.5); } .infobar-button:active { - background: rgba(126,87,194, 0.4); + background: rgba(126,87,194, 0.8); } .infobar-button:disabled { @@ -296,7 +296,7 @@ a:visited { border: none; outline: 0; transition-duration: 0.3s; - background: rgba(0, 0, 0, 0.2); + background: rgba(126,87,194, 0.2); color: rgb(255, 255, 255); font-size: calc(3 * var(--border-radius)); height: calc(4 * var(--border-radius)); From fea8e43ee2f6942fa7c5c851f589a5b058a6869c Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Sun, 24 Mar 2019 16:43:29 +0100 Subject: [PATCH 16/22] friends --- index.html | 1 + infocontainer.js | 27 ++++++++++++ main.js | 27 +++++++----- online_match_manager.js | 36 ++++++++++++--- style.css | 68 +++++++++++++++++++---------- sw.js | 1 + tools.js | 13 ++++++ websocket_connection.js | 97 ++++++++++++++++++++++++++++++++++++++++- 8 files changed, 230 insertions(+), 40 deletions(-) create mode 100644 tools.js diff --git a/index.html b/index.html index 3d188ea..8e834d7 100644 --- a/index.html +++ b/index.html @@ -10,6 +10,7 @@
+ diff --git a/infocontainer.js b/infocontainer.js index f72ffa4..748abc1 100644 --- a/infocontainer.js +++ b/infocontainer.js @@ -37,6 +37,33 @@ class Infocontainer return l; } + create_double_button(text, option) + { + var div = document.createElement("div"); + div.className = "option-button-container"; + var b1 = document.createElement("button"); + var b2 = document.createElement("button"); + + b1.style.width = "10%"; + + b1.className = "infobar-button"; + b2.className = "infobar-button"; + + b1.style.width = "80%"; + b2.style.width = "20%"; + + + b1.appendChild(document.createTextNode(text)); + b2.appendChild(document.createTextNode(option)); + + div.appendChild(b1); + div.appendChild(b2); + + this.container.appendChild(div); + + return [div, b1,b2]; + } + hide() { this.container.style.display = "none"; diff --git a/main.js b/main.js index 1165331..9978035 100644 --- a/main.js +++ b/main.js @@ -22,37 +22,42 @@ create_game_container = main_menu.create_infocontainer(); create_game_container.create_label("Start Local Game"); b_local_game = create_game_container.create_button("Local Game"); + + // register container: register_container = main_menu.create_infocontainer(); register_container.create_label("Login to play online"); i_register_username = register_container.create_input("username"); i_register_pw = register_container.create_input("password", true); b_register = register_container.create_button("register/login"); -register_container.create_label("(creates new account for a new username)"); +//register_container.create_label("(creates new account for a new username)"); // logout: -logout_container = sub_menu.create_infocontainer(); +logout_container = main_menu.create_infocontainer(); l_username = logout_container.create_label("logged in as: "); b_logout = logout_container.create_button("logout"); -// match control -match_control = sub_menu.create_infocontainer(); -b_end_game = match_control.create_button("Close Match"); - // fill subcontainer: match_slot_container = sub_menu.create_infocontainer(); match_slot_container.create_label("Running Matches
(click to open)"); + +// local match control: +match_control = sub_menu.create_infocontainer(); +b_end_game = match_control.create_button("Close Match"); + + // search match: -search_match_container = main_menu.create_infocontainer(); +search_match_container = sub_menu.create_infocontainer(); search_match_container.create_label("Create Online Match"); b_match_search = search_match_container.create_button("random match"); -search_match_container.create_label("

"); l_match_op = search_match_container.create_input("player name"); b_match_invite = search_match_container.create_button("invite player"); +search_match_container.create_label("Invite friends:") + //status: @@ -222,7 +227,7 @@ reconnect = function() { connection.close(); } - connection = new WebsocketConnection(server_url, server_port, grid, l_status, match_slot_container, match_control, login_callback, on_connection_error); + connection = new WebsocketConnection(server_url, server_port, grid, l_status, match_slot_container, match_control, search_match_container, login_callback, on_connection_error); connection.reconnect(session_id); } } @@ -239,7 +244,7 @@ login = function(){ { connection.close(); } - connection = new WebsocketConnection(server_url, server_port, grid, l_status, match_slot_container, match_control, login_callback, on_connection_error); + connection = new WebsocketConnection(server_url, server_port, grid, l_status, match_slot_container, match_control, search_match_container, login_callback, on_connection_error); connection.connect(i_register_username.value.toLowerCase(), i_register_pw.value); } @@ -266,6 +271,8 @@ invite_player = function() } } + + // initiate stuff and connect events: end_local_game(); diff --git a/online_match_manager.js b/online_match_manager.js index d80c93d..b65d9af 100644 --- a/online_match_manager.js +++ b/online_match_manager.js @@ -21,10 +21,27 @@ class OnlineMatchManager // create match button in match list: this.control_container = control_container; - this.matches_container = matches_container - this.match_button = matches_container.create_button("" + this.online_opponent.get_name()); + this.matches_container = matches_container; + + var tmp = matches_container.create_double_button("" + this.online_opponent.get_name(), "+") + + this.match_button = tmp[1]; + this.match_button_div = tmp[0]; + this.match_button_option = tmp[2]; + + if (this.game_server_connection.is_friend(this.online_opponent.get_name())) + { + this.match_button_option.disabled = true; + } + else + { + this.match_button_option.addEventListener("click", () => { + this.game_server_connection.send_friend_request(this.online_opponent.get_name()); + this.match_button_option.disabled = true; + }); + } + this.match_button.addEventListener("click", () => this.open_match()); - this.match_button.style.background = "rbg(0,255,0)"; if (this.online_opponent.get_name() == this.match_state.active_player) { @@ -57,7 +74,12 @@ class OnlineMatchManager this.game_server_connection.send_end_match(this.match_id); } - this.matches_container.container.removeChild(this.match_button); + if (this.match_button_div != null) + { + clearInner(this.match_button_div) + this.matches_container.container.removeChild(this.match_button_div); + this.match_button_div = null; + } this.status_label.innerHTML = "match is closed"; this.control_container.hide(); this.is_closed = true; @@ -66,9 +88,11 @@ class OnlineMatchManager remove_match() { - if (!this.is_closed) + if (this.match_button_div != null) { - this.matches_container.container.removeChild(this.match_button); + clearInner(this.match_button_div) + this.matches_container.container.removeChild(this.match_button_div); + this.match_button_div = null; } } diff --git a/style.css b/style.css index ee17b99..b45ba66 100644 --- a/style.css +++ b/style.css @@ -162,23 +162,41 @@ a:visited { text-align: center; background: none; white-space: normal; + overflow-y: scroll; +} + +@media (max-aspect-ratio: 1/1) { + + .info-container{ + width: calc(0.3 * var(--sidebar-width)); + } } .infobar-label { color: rgba(224, 217, 235, 0.8); + font-size: calc(2 * var(--border-radius)); + margin-left: 0; + margin-right: 0; + width: 100%; display: flex; flex-direction: column; - font-size: calc(2 * var(--border-radius)); - width: calc(var(--sidebar-width) - 4 * var(--border-radius)); + text-align: center; + } -@media (max-aspect-ratio: 1/1) { - .infobar-label - { - width: calc(var(--sidebar-width) * 0.25); - } +.option-button-container +{ + border-radius: 0%; + margin: 0%; + top: 0%; + display: flex; + flex-direction:row; + padding: none; } + + + .infobar-button { border-radius: var(--border-radius); margin: var(--border-radius); @@ -189,9 +207,12 @@ a:visited { color: rgb(255, 255, 255); font-size: calc(2.8 * var(--border-radius)); height: calc(4 * var(--border-radius)); - width: calc(var(--sidebar-width) - 4 * var(--border-radius)); - vertical-align: middle; + /* width: calc(var(--sidebar-width) - 4 * var(--border-radius)); */ + margin-left: 0; + margin-right: 0; + width: 100%; display: flex; + vertical-align: middle; flex-direction: column; text-align: center; } @@ -199,7 +220,6 @@ a:visited { @media (max-aspect-ratio: 1/1) { .infobar-button{ - width: calc(0.25 * var(--sidebar-width)); height: calc(0.18 * var(--sidebar-height)); } } @@ -214,16 +234,17 @@ a:visited { color: rgb(255, 255, 255); font-size: calc(2.8 * var(--border-radius)); height: calc(4 * var(--border-radius)); - width: calc(var(--sidebar-width) - 4 * var(--border-radius)); - vertical-align: middle; + margin-left: 0; + margin-right: 0; + width: 100%; display: flex; + vertical-align: middle; flex-direction: column; } @media (max-aspect-ratio: 1/1) { .infobar-button-green{ - width: calc(0.25 * var(--sidebar-width)); height: calc(0.18 * var(--sidebar-height)); } } @@ -238,16 +259,17 @@ a:visited { color: rgb(255, 255, 255); font-size: calc(2.8 * var(--border-radius)); height: calc(4 * var(--border-radius)); - width: calc(var(--sidebar-width) - 4 * var(--border-radius)); - vertical-align: middle; + margin-left: 0; + margin-right: 0; + width: 100%; display: flex; + vertical-align: middle; flex-direction: column; } @media (max-aspect-ratio: 1/1) { .infobar-button-red{ - width: calc(0.25 * var(--sidebar-width)); height: calc(0.18 * var(--sidebar-height)); } } @@ -262,16 +284,17 @@ a:visited { color: rgb(255, 255, 255); font-size: calc(2.8 * var(--border-radius)); height: calc(4 * var(--border-radius)); - width: calc(var(--sidebar-width) - 4 * var(--border-radius)); - vertical-align: middle; + margin-left: 0; + margin-right: 0; + width: 100%; display: flex; + vertical-align: middle; flex-direction: column; } @media (max-aspect-ratio: 1/1) { .infobar-button-active{ - width: calc(0.25 * var(--sidebar-width)); height: calc(0.18 * var(--sidebar-height)); } } @@ -300,9 +323,11 @@ a:visited { color: rgb(255, 255, 255); font-size: calc(3 * var(--border-radius)); height: calc(4 * var(--border-radius)); - width: calc(var(--sidebar-width) - 4 * var(--border-radius)); - vertical-align: middle; + margin-left: 0; + margin-right: 0; + width: 100%; display: flex; + vertical-align: middle; flex-direction: column; text-align: center; } @@ -315,6 +340,5 @@ a:visited { .infobar-input{ height: calc(0.18 * var(--sidebar-height)); - width: calc(0.25 * var(--sidebar-width)); } } \ No newline at end of file diff --git a/sw.js b/sw.js index bfc5a46..6b4aaf8 100644 --- a/sw.js +++ b/sw.js @@ -7,6 +7,7 @@ self.addEventListener('install', function (e) { caches.open('your-magic-cache').then(function (cache) { return cache.addAll([ '/website/ultimate_tictactoe/', + '/website/ultimate_tictactoe/tools.js', '/website/ultimate_tictactoe/index.html', '/website/ultimate_tictactoe/manifest.json', '/website/ultimate_tictactoe/icon.png', diff --git a/tools.js b/tools.js new file mode 100644 index 0000000..211df84 --- /dev/null +++ b/tools.js @@ -0,0 +1,13 @@ + +function clear(node) { + while (node.hasChildNodes()) { + clear(node.firstChild); + } + node.parentNode.removeChild(node); +} + +function clearInner(node) { + while (node.hasChildNodes()) { + clear(node.firstChild); + } +} \ No newline at end of file diff --git a/websocket_connection.js b/websocket_connection.js index 26f4dc7..2223fdb 100644 --- a/websocket_connection.js +++ b/websocket_connection.js @@ -1,6 +1,6 @@ class WebsocketConnection { - constructor(ip, port, grid, status_label, matches_container, control_container, login_callback_func, error_callback_func) + constructor(ip, port, grid, status_label, matches_container, control_container, search_container, login_callback_func, error_callback_func) { this.ip = ip; this.port = port; @@ -13,6 +13,7 @@ class WebsocketConnection this.status_label = status_label; this.matches_container = matches_container; this.control_container = control_container; + this.search_container = search_container; this.active_match = null; @@ -27,8 +28,12 @@ class WebsocketConnection this.closed_by_user = false; - matches_container.hide(); + this.friends = []; + + this.friend_name_divs = []; + + matches_container.hide(); } @@ -52,6 +57,11 @@ class WebsocketConnection return this.connected; } + is_friend(friend) + { + return this.friends.includes(friend); + } + on_open(username, pw) { this.connected = true; @@ -120,6 +130,18 @@ class WebsocketConnection case "match_request_response": this.on_match_request_response(msg.data); break; + + case "friend_request_response": + this.on_friend_request_response(msg.data); + break; + + case "unfriend_request_response": + this.on_unfriend_request_response(msg.data); + break; + + case "friends_update": + this.on_friends_update(msg.data); + break; } } @@ -239,6 +261,51 @@ class WebsocketConnection } } + on_friend_request_response(data) + { + this.status_label.innerHTML = data.msg; + } + + on_unfriend_request_response(data) + { + this.status_label.innerHTML = data.msg; + } + + on_friends_update(data) + { + this.friends = data.friends; + + // remove complete friend list: + var n = this.friend_name_divs.length; + + var i; + + for (i = 0; i < n; i++) + { + clearInner(this.friend_name_divs[i]); + this.search_container.container.removeChild(this.friend_name_divs[i]); + this.friend_name_divs[i] = null; + } + this.friend_name_divs = []; + // rebuild friend list: + n = this.friends.length; + + for (i = 0; i < n; i++) + { + var tmp = this.search_container.create_double_button("" + this.friends[i], "-"); + var name = this.friends[i]; + + tmp[1].addEventListener("click", () => { + this.send_match_request(name); + }); + + tmp[2].addEventListener("click", () => { + this.send_unfriend_request(name); + }); + + this.friend_name_divs.push(tmp[0]) + } + } connect(username, pw) { @@ -303,6 +370,28 @@ class WebsocketConnection this.socket.send(JSON.stringify(msg_object)); } + send_friend_request(friend_name) + { + var msg_object = { + type: "friend_request", + data: { + user: friend_name + } + }; + this.socket.send(JSON.stringify(msg_object)); + } + + send_unfriend_request(friend_name) + { + var msg_object = { + type: "unfriend_request", + data: { + user: friend_name + } + }; + this.socket.send(JSON.stringify(msg_object)); + } + login(username, pw) { this.player.set_name(username); @@ -320,6 +409,10 @@ class WebsocketConnection relogin(session_id) { + for (var key in this.openmatches) + { + this.openmatches[key].remove_match(); + } // register for game queue var msg_object = { type: "reconnect", From 1e2db5efa46a150b6b51c4bdd0082951c61b609b Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Sun, 24 Mar 2019 18:11:55 +0100 Subject: [PATCH 17/22] style improvements --- style.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/style.css b/style.css index b45ba66..86e5b29 100644 --- a/style.css +++ b/style.css @@ -70,7 +70,7 @@ a:visited { .main-container { white-space: normal; - padding-top: calc(50vh - 0.5 * var(--board-size) - var(--sidebar-height) - 2 * var(--button-margin)); + padding-top: 0; } } @@ -149,6 +149,8 @@ a:visited { .infobar-container { flex-direction:row; padding: none; + min-height: var(--sidebar-height); + height: calc((100vh - var(--board-size)) * 0.5 - 2* var(--border-radius)); } } From a790cb462ad5fb02704feead9fbc696c05ff6fb0 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Sun, 24 Mar 2019 18:21:34 +0100 Subject: [PATCH 18/22] updated icon --- icon.png | Bin 7153 -> 19200 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icon.png b/icon.png index 84a6070fd77ead8cad51d760da4248ed966e3011..3e76b6ac505087a7fd1cc268e6b887300fa3d3bd 100644 GIT binary patch literal 19200 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
;}W)Fj$ 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

1vcP~{w8G6i;=Hpo8R6SHe6vw&yiFw0 zxsCbpnJ+h_DzwFT?@@C^d--xnc3tXCPwHG}%LZ(!`&@j2S}n`clN1frLu|e3e$;Bw zqeiH+_P|rYwxmqYHXw7|+Z}2db-3zx3VGE<72lq_PB(5mH{Kn14Sv$b?@(yEic5aR z^bVe_I3U5UPV<;7+M8IL>u6lE#$&njgL6`%Apy8^gZ;^wq@Jz&>?%x!nVe!s@iI7A zF8eS3R(I7S#0xVT`qoCs{CKl+dF(qX%95;xJD$tSyZZkVF=US4_ zTvmxZllzm50|huYy2}!!qnmj$9}xJjeLE9S4(1S7Pb{9dqAGECEueJ&X!l~|Jn=be3;zm<+*V(68JPWwKL6%N%SjNO3(TJ53x}k;% zQO`{6Zsu1WFP`=L-=N{>H?QV9g2Xmrbr{YUPH!|3Ifr%(QcTySlSFx>x^M-NT&=5D zPi}kpNH~roM>Sxh^9p?OHRjyvm1v~i9zK4Xod_x{nL7;*z zt%SlxE!gOZ=5OLKV4z^~JR*i9z{cO7e+iawjjY}drO7>;d zi0(Ns;M42Mc1x47*<%gbwdYd%4PnL7?MAwuZ9`T$4l%~>L2|+0YD-b;)=^w^1$l+h_O|9(b=>TOQ3%mSEZ_JtQAO3cO{weZg z_f-5>w6`>VmT@ohjc<8%vue)qz#B`?W=;veTbg-e{UHmy?A$}l*#AU4q14*y$rlMH ziR}kIFmsiH?U4u1H`FrY*;b*ACe`8ZglQ()v^Pz&@a2M8PSgHa9KsIoqMfx$Z*#52 z3YeG=*g^C?GdH)q_3-(@vEQlnwReVT&wa6Th#h+84O~W)?BWRRI%pxCyNK2oK$(dV zP2J$nExPzwIE~zTBtGBCOQbu$x&WJV;8GTy5^aLcTmTV|Z1t0Kj12AF-Bo|z zOUR~I;T{A>mzJv-mi;Gc&fh*3BFIX-~r4P#AmDK>(zvsww^VNtSj$&st*?=oo=#~ zr@VHi5^bYOT84O!W;k6o+&6zvc3puSn@K$Ic2vk{=AVYMc0yMo50Ap>3JIgDjpTi>bc9eLw7&VBL$0zDxyT-7G0h4iu?{bFS7w7L7^jORv1AtqZ)L@iPWncEM}1Em9I zcctw Date: Sun, 24 Mar 2019 18:21:39 +0100 Subject: [PATCH 19/22] little fix --- websocket_connection.js | 1 + 1 file changed, 1 insertion(+) diff --git a/websocket_connection.js b/websocket_connection.js index 2223fdb..f035023 100644 --- a/websocket_connection.js +++ b/websocket_connection.js @@ -446,6 +446,7 @@ class WebsocketConnection { this.openmatches[key].remove_match(); } + this.openmatches = {}; this.status_label.innerHTML = "logged out"; this.closed_by_user = true; this.socket.close(); From 80b8ff8f93a7db0c01e9d5db0c57562ddcc81bc3 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Sun, 24 Mar 2019 18:28:14 +0100 Subject: [PATCH 20/22] again a fix --- websocket_connection.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/websocket_connection.js b/websocket_connection.js index f035023..6c8716b 100644 --- a/websocket_connection.js +++ b/websocket_connection.js @@ -293,14 +293,13 @@ class WebsocketConnection for (i = 0; i < n; i++) { var tmp = this.search_container.create_double_button("" + this.friends[i], "-"); - var name = this.friends[i]; tmp[1].addEventListener("click", () => { - this.send_match_request(name); + this.send_match_request("" +this.friends[i]); }); tmp[2].addEventListener("click", () => { - this.send_unfriend_request(name); + this.send_unfriend_request("" +this.friends[i]); }); this.friend_name_divs.push(tmp[0]) From 3a22acf5100302af807e22518da45486ca386620 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Sun, 24 Mar 2019 18:55:06 +0100 Subject: [PATCH 21/22] fixes again --- websocket_connection.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/websocket_connection.js b/websocket_connection.js index 6c8716b..c65e873 100644 --- a/websocket_connection.js +++ b/websocket_connection.js @@ -293,19 +293,27 @@ class WebsocketConnection for (i = 0; i < n; i++) { var tmp = this.search_container.create_double_button("" + this.friends[i], "-"); + + tmp[1].name = this.friends[i]; + tmp[2].name = this.friends[i]; + tmp[1].connection = this; + tmp[2].connection = this; - tmp[1].addEventListener("click", () => { - this.send_match_request("" +this.friends[i]); + + tmp[1].addEventListener("click", function() { + this.connection.send_match_request(this.name); }); - tmp[2].addEventListener("click", () => { - this.send_unfriend_request("" +this.friends[i]); + tmp[2].addEventListener("click", function (){ + this.connection.send_unfriend_request(this.name); }); this.friend_name_divs.push(tmp[0]) } } + + connect(username, pw) { this.socket = new WebSocket(server_protocol + this.ip + ":" + this.port); From dffdf613e6017764b1d2ad9be59e5bcb1df35e39 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Sun, 24 Mar 2019 19:10:46 +0100 Subject: [PATCH 22/22] more fixes --- online_match_manager.js | 2 +- websocket_connection.js | 47 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/online_match_manager.js b/online_match_manager.js index b65d9af..768631b 100644 --- a/online_match_manager.js +++ b/online_match_manager.js @@ -90,7 +90,7 @@ class OnlineMatchManager { if (this.match_button_div != null) { - clearInner(this.match_button_div) + clearInner(this.match_button_div); this.matches_container.container.removeChild(this.match_button_div); this.match_button_div = null; } diff --git a/websocket_connection.js b/websocket_connection.js index c65e873..36b2552 100644 --- a/websocket_connection.js +++ b/websocket_connection.js @@ -64,18 +64,40 @@ class WebsocketConnection on_open(username, pw) { + this.connected = true; this.login(username, pw) } on_reopen(session_id) { + this.connected = true; this.relogin(session_id); } on_close(login_failed=false) { + for (var key in this.openmatches) + { + this.openmatches[key].remove_match(); + } + this.openmatches = {}; + + // remove complete friend list: + var n = this.friend_name_divs.length; + + var i; + + for (i = 0; i < n; i++) + { + clearInner(this.friend_name_divs[i]); + this.search_container.container.removeChild(this.friend_name_divs[i]); + this.friend_name_divs[i] = null; + } + this.friend_name_divs = []; + this.friends = []; + var login_failed = !this.registered; this.registered = false; this.connected = false; @@ -89,6 +111,26 @@ class WebsocketConnection on_error() { + for (var key in this.openmatches) + { + this.openmatches[key].remove_match(); + } + this.openmatches = {}; + + // remove complete friend list: + var n = this.friend_name_divs.length; + + var i; + + for (i = 0; i < n; i++) + { + clearInner(this.friend_name_divs[i]); + this.search_container.container.removeChild(this.friend_name_divs[i]); + this.friend_name_divs[i] = null; + } + this.friend_name_divs = []; + this.friends = []; + console.log("error in websocket connection"); this.registered = false; this.connected = false; @@ -449,11 +491,6 @@ class WebsocketConnection close() { - for (var key in this.openmatches) - { - this.openmatches[key].remove_match(); - } - this.openmatches = {}; this.status_label.innerHTML = "logged out"; this.closed_by_user = true; this.socket.close();