merged feature/user_management into master

This commit is contained in:
Jonas Weinz 2019-03-24 21:24:00 +01:00
commit dcc9472906
16 changed files with 1513 additions and 535 deletions

View File

@ -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();
}
}
}

33
grid.js
View File

@ -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)
@ -81,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++;
}
@ -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();

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -2,29 +2,25 @@
<link rel="stylesheet" type="text/css" href="style.css">
<head>
<meta name="viewport" content="width=device-width, user-scalable=no" />
<meta name="theme-color" content="#7e57c2" />
<link rel="manifest" href="./manifest.json" />
<link rel="icon" href="icon.png" type="image/png"/>
<script defer src="site.js"></script>
<title>ultimate tictactoe</title>
</head>
<body>
<div id="main-container" class="main-container">
<div id="grid-container" class="grid-container"></div>
<div id="sidebar-container" class="sidebar-container">
<div id="create-game-container" class="create-game-container"></div>
<div id="setting-container" class="setting-container"></div>
<div id="control-container" class="control-container"></div>
<div id="info-container" class="info-container"></div>
</div>
</div>
<div id="main-container" class="main-container"></div>
<script src="tools.js"></script>
<script src="settings.js"></script>
<script src="player.js"></script>
<script src="tile.js"></script>
<script src="grid.js"></script>
<script src="subgrid.js"></script>
<script src="game_manager.js"></script>
<script src="game_server_connection.js"></script>
<script src="sidebar.js"></script>
<script src="local_match_manager.js"></script>
<script src="online_match_manager.js"></script>
<script src="websocket_connection.js"></script>
<script src="infobar.js"></script>
<script src="infocontainer.js"></script>
<script src="main.js"></script>
</body>

15
infobar.js Normal file
View File

@ -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);
}
}

76
infocontainer.js Normal file
View File

@ -0,0 +1,76 @@
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;
}
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";
}
show()
{
this.container.style.display = "inline-block";
}
}

72
local_match_manager.js Normal file
View File

@ -0,0 +1,72 @@
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_label.innerHTML = "" + grid.get_won_player().get_name() + " has won.";
this.end_game();
}
else if (grid.is_complete())
{
this.status_label.innerHTML = "Draw. Everybody looses!";
this.end_game(false);
}
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(closed_by_player = true)
{
if (closed_by_player)
{
this.status_label.innerHTML = "Game Over. Game Closed";
}
this.grid.block_all();
}
}

304
main.js
View File

@ -3,18 +3,304 @@ 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("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 = main_menu.create_infocontainer();
l_username = logout_container.create_label("logged in as: ");
b_logout = logout_container.create_button("logout");
// fill subcontainer:
match_slot_container = sub_menu.create_infocontainer();
match_slot_container.create_label("Running Matches<br>(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 = sub_menu.create_infocontainer();
search_match_container.create_label("Create Online Match");
b_match_search = search_match_container.create_button("random match");
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:
status_container = main_menu.create_infocontainer();
l_status_head = status_container.create_label("Status:");
l_status = status_container.create_label("select gamemode. click <br> <a href=https://en.wikipedia.org/wiki/Ultimate_tic-tac-toe#Rules>[here]</a> for the rules!");
// global vars:
game_manager = null;
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;
}
// 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();
match_control.hide();
}
}
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();
set_cookie("sessionid", connection.session_id, 30);
}
logout = function()
{
logged_in = false;
if (connection != null)
{
connection.close();
if (check_cookie("sessionid"))
{
// delete session:
session_id = get_cookie("sessionid");
set_cookie("sessionid", session_id, -100);
session_id = null;
}
}
connection = null;
grid.unblock_all();
grid.deactivate_all();
grid.block_all();
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, search_match_container, 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, search_match_container, login_callback, on_connection_error);
connection.connect(i_register_username.value.toLowerCase(), i_register_pw.value);
}
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);
reconnect();
// 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();
}
};
};

View File

@ -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",

257
online_match_manager.js Normal file
View File

@ -0,0 +1,257 @@
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;
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());
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;
}
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);
}
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;
}
remove_match()
{
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;
}
}
update_match_state(match_state)
{
this.match_state = match_state;
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())
{
this.game_server_connection.notify("your turn against " + this.online_opponent.get_name());
}
}
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);
this.grid.deactivate_all();
this.grid.unblock_all();
this.match_button.className = "infobar-button-active";
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)
{
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;
}
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";
}
}
}

View File

@ -1,4 +1,6 @@
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";
var server_url = "the-cake-is-a-lie.net";
var server_port = "5555";
var rel_home = "/website/ultimate_tictactoe";

View File

@ -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. <br> <a href=https://en.wikipedia.org/wiki/Ultimate_tic-tac-toe#Rules>here are the rules </a>");
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 = "<p>" + title + "</p>";
}
}
}

271
style.css
View File

@ -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: 0;
}
}
@ -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,17 @@ 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;
min-height: var(--sidebar-height);
height: calc((100vh - var(--board-size)) * 0.5 - 2* var(--border-radius));
}
}
.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);
@ -176,78 +164,183 @@ a:visited {
text-align: center;
background: none;
white-space: normal;
overflow-y: scroll;
}
.sidebar-label {
color: white;
display: flex;
flex-direction: column;
@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));
width: calc(var(--sidebar-width) - 4 * var(--border-radius));
text-align: left;
}
@media (orientation: portrait) {
.sidebar-label
{
width: calc(var(--sidebar-width) * 0.3);
}
}
.sidebar-button {
border-radius: var(--border-radius);
margin: var(--border-radius);
border: none;
outline: 0;
transition-duration: 0.3s;
background: rgba(0, 0, 0, 0.5);
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;
flex-direction: column;
text-align: center;
}
@media (orientation: portrait) {
.sidebar-button{
width: calc(0.25 * var(--sidebar-width));
height: calc(0.3 * var(--sidebar-height));
}
.option-button-container
{
border-radius: 0%;
margin: 0%;
top: 0%;
display: flex;
flex-direction:row;
padding: none;
}
.sidebar-button:hover {
background: rgba(255, 255, 255, 0.2);
}
.sidebar-button:active {
background: rgba(255, 255, 255, 0.4);
}
.sidebar-input {
.infobar-button {
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(126,87,194, 0.2) ;
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;
/* 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;
}
@media (orientation: portrait) {
@media (max-aspect-ratio: 1/1) {
.sidebar-input{
height: calc(0.3 * var(--sidebar-height));
width: calc(0.25 * var(--sidebar-width));
.infobar-button{
height: calc(0.18 * var(--sidebar-height));
}
}
.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));
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{
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));
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{
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(126,87,194, 0.8) ;
color: rgb(255, 255, 255);
font-size: calc(2.8 * var(--border-radius));
height: calc(4 * var(--border-radius));
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{
height: calc(0.18 * var(--sidebar-height));
}
}
.infobar-button:hover {
background: rgba(126,87,194, 0.5);
}
.infobar-button:active {
background: rgba(126,87,194, 0.8);
}
.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(126,87,194, 0.2);
color: rgb(255, 255, 255);
font-size: calc(3 * var(--border-radius));
height: calc(4 * var(--border-radius));
margin-left: 0;
margin-right: 0;
width: 100%;
display: flex;
vertical-align: middle;
flex-direction: column;
text-align: center;
}
.infobar-input::placeholder {
color: rgba(255,255,255,0.3);
}
@media (max-aspect-ratio: 1/1) {
.infobar-input{
height: calc(0.18 * var(--sidebar-height));
}
}

73
sw.js
View File

@ -1,37 +1,44 @@
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/game_manager.js',
'/website/ultimate_tictactoe/game_server_connection.js',
'/website/ultimate_tictactoe/sidebar.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);
})
);
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) {
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',
'/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('notificationclick', function (event) {
console.log ("push message clicked");
});
event.notification.close();
clients.openWindow(home);
});

13
tools.js Normal file
View File

@ -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);
}
}

519
websocket_connection.js Normal file
View File

@ -0,0 +1,519 @@
class WebsocketConnection
{
constructor(ip, port, grid, status_label, matches_container, control_container, search_container, login_callback_func, error_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.search_container = search_container;
this.active_match = null;
this.connected = false;
this.current_end_button = null;
this.login_callback_func = login_callback_func;
this.openmatches = {};
this.error_callback_func = error_callback_func;
this.closed_by_user = false;
this.friends = [];
this.friend_name_divs = [];
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;
}
is_friend(friend)
{
return this.friends.includes(friend);
}
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;
if (!this.closed_by_user && !login_failed)
{
this.status_label.innerHTML = "connection to server closed";
this.error_callback_func();
}
}
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;
if (!this.closed_by_user)
{
this.status_label.innerHTML = "connection to server lost";
this.error_callback_func();
}
}
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 "reconnect_response":
this.on_reconnect_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;
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;
}
}
set_active_match(id)
{
for (var key in this.openmatches)
{
if (key == id)
{
continue;
}
this.openmatches[key].on_focus_loose();
}
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();
}
if (this.current_end_button != null)
{
this.control_container.container.deleteChild(this.current_end_button);
this.current_end_button = null;
}
}
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)
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());
if (match_state.last_move == null)
{
this.notify("new Game against " + this.openmatches[id].online_opponent.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_reconnect_response(data)
{
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)
{
if (data.success)
{
this.status_label.innerHTML = "match request sent";
}
else
{
this.status_label.innerHTML = "could not send request: " + data.msg;
}
}
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], "-");
tmp[1].name = this.friends[i];
tmp[2].name = this.friends[i];
tmp[1].connection = this;
tmp[2].connection = this;
tmp[1].addEventListener("click", function() {
this.connection.send_match_request(this.name);
});
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);
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)
{
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));
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));
}
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);
// register for game queue
var msg_object = {
type: "login",
data: {
name: this.player.get_name(),
pw: pw
}
};
this.socket.send(JSON.stringify(msg_object));
}
relogin(session_id)
{
for (var key in this.openmatches)
{
this.openmatches[key].remove_match();
}
// 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)
{
return;
}
var msg_object = {
type: "end_game",
data: {
msg: ""
}
};
this.socket.send(JSON.stringify(msg_object));
}
close()
{
this.status_label.innerHTML = "logged out";
this.closed_by_user = true;
this.socket.close();
}
notify(text) {
if (document.hasFocus())
{
return;
}
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"
});
});
}
});
}
}