From 0539b0b11448349ae8838fab118c0a9f037abd42 Mon Sep 17 00:00:00 2001 From: Wanja Date: Mon, 9 Apr 2012 01:29:42 +0200 Subject: [PATCH] * Multiple games at once * New theme * Network games somewhat broken --- assets/www/css/jq.ui.eco.css | 921 +++++++++++++++++++++++++++++++++++ assets/www/css/ui.css | 17 +- assets/www/index.html | 33 +- assets/www/js/net.js | 12 +- assets/www/js/tafl.js | 73 +-- assets/www/js/tools.js | 14 + assets/www/js/ui.js | 280 ++++++++--- server/tafl_server.js | 25 +- 8 files changed, 1247 insertions(+), 128 deletions(-) create mode 100644 assets/www/css/jq.ui.eco.css create mode 100644 assets/www/js/tools.js diff --git a/assets/www/css/jq.ui.eco.css b/assets/www/css/jq.ui.eco.css new file mode 100644 index 0000000..93e56bc --- /dev/null +++ b/assets/www/css/jq.ui.eco.css @@ -0,0 +1,921 @@ +/********************************************************** + GENERAL UI ELEMENTS +**********************************************************/ + +* { + -webkit-user-select:none; /* Prevent copy paste for all elements except text fields */ + -webkit-tap-highlight-color:rgba(255, 255, 255, 0); /* set highlight color for user interaction */ + margin:0; + padding:0;/* Remove default padding and margins for all elements */ + -webkit-box-sizing:border-box; box-sizing:border-box; +} + +input,textarea { -webkit-user-select:text; } /* allow users to select text that appears in input fields */ + +img { border:none; } /* Remove default borders for images */ + +body { + overflow-x:hidden; + -webkit-text-size-adjust:none; + font:normal 14px/18px Arial, Helvetica, sans-serif; + color:#36251B; + background:#9EA172; /* primary background color */ + display:-webkit-box; /* We want to layout our first container vertically */ + -webkit-box-orient:vertical; /* we want our child elements to stretch to fit the container */ + -webkit-box-align:stretch; +} /* General styles that apply to elements not contained within other classes and styles */ + +p { + font:normal 14px/18px Arial, Helvetica, sans-serif; + color:#36251B; + background:#9EA172; + padding:16px; +} /* Paragraph class used for text areas. */ + + +#jQui_modal { + -webkit-transform:translate3d(100%,0,0); + -webkit-backface-visibility:hidden; + z-index:9999 !important; + width:100%; height:100%; + display:none; + position:absolute; top:0px; left:-100%; + overflow:hidden; + background:rgba(54,37,27,1) !important; + -webkit-perspective:1000; +} + +#modalContainer { +width:100%; +} +.horzRule { + position:relative; + display:block; + background:#E6E2B5; + width:100%; height:1px; +} /* Horizontal line. */ + +.jqmScrollbar { background:#E6E2B5 !important; } /* Sets the color of the scrollbar */ + +#jQUi { + position:absolute; + width:100%; + top:0px; + bottom:0px; +} + +#jQUi > #splashscreen { + position:absolute; + top:0px;bottom:0px; + width:100%; + left:0px; + min-height:100%; + background:#9EA172 !important; + color:#E6E2B5 !important; + font-size:30px; + text-align:center; + z-index:9999; + display:block; + margin-left:auto !important; margin-right:auto !important; + padding-top:80px !important; +} + + + +/********************************************************** + header +**********************************************************/ + +#jQUi > #header { + display:block; + z-index:250; + -webkit-box-sizing:border-box; box-sizing:border-box; + height:48px; + left:0px;right:0px; + background-color:#424D00; + background-image:-webkit-gradient(linear, left top, left bottom, from(#798E19), to(#424D00)); + -webkit-box-shadow:0px 0px 24px rgba(54,37,27,0.4); box-shadow:0px 0px 24px rgba(54,37,27,0.4); +} /* This is masthead bar that appears at the top of the UI */ + +#header > h1 { + position:absolute; + overflow:hidden; + width:100%; + z-index:-11; + left:-10px; + text-align:center; + height:48px; + font:bold 24px/44px Arial, Helvetica, sans-serif; + color:#E6E2B5; + text-shadow:0px -1px rgba(54,37,27,.5); + text-align:center; + text-overflow:ellipsis; + white-space:nowrap; +} /* This is text that appears in the header at the top of the screen */ + +body[orient="landscape"] > #header > h1 { + margin-left:-125px; + width:250px; +} /* Updates the available text area and positioning when the screen is held in landscape orientation. */ + + + +/********************************************************** + BUTTON STYLES +**********************************************************/ +#backButton { + display:block; + position:absolute; + left:0px; + top:0px; + max-width:60px; + text-overflow:ellipsis; +} /* Sets up positioning of the back button which appears in the header */ + +#backButton > div { + max-width:60px; + overflow:hidden; + text-overflow:ellipsis; +} /* sets up sizing and handling of excess text for the type in the back button */ + +.button { + position:relative; + display:inline-block; + height:36px; min-width:60px; + cursor:pointer; + font:bold 14px/34px Helvetica, Arial, sans-serif; + color:#E6E2B5; + text-decoration:none; + text-align:center; + text-shadow:0px -1px 0px rgba(54,37,27,.5); + padding:0px 8px; + border:1px solid #7C543B; + margin-top:6px; margin-left:6px; + text-overflow:ellipsis; + -webkit-border-radius:6px; border-radius:6px; + background:#7C543B; + background-image:-webkit-gradient(linear, left top, left bottom, from(#A48161), to(#7C543B)); +} /* Styling for CSS-generated buttons, including the back button. These buttons can bue used anywhere in the UI. */ + +.button.pressed { + color:#fff; + text-shadow:0px -1px 0px rgba(54,37,27,1); + background-color:rgba(50,50,50,1); + border:1px solid rgba(54,37,27,1); +} /* behavior of button for touch interaction */ + +.modalbutton { + position:absolute; + top:0px; + right:5px; + height:32px; + width:58px; + line-height:32px; + z-index:9999; +} + + +/********************************************************** + CONTENT AREA +**********************************************************/ +#content{ + z-index:180; + display:block; + position:absolute; + top:48px; + bottom:62px; + left:0px;right:0px; +} /* Accounts for positioning of the content area, which is everything below the header and above the navbar. */ + +.panel { + -webkit-backface-visibility:hidden; + z-index:180; + width:100%; + height:100%; + display:none; + position:absolute; top:0px; left:-100%; + overflow:hidden; + background:transparent; + -webkit-perspective:1000; +} /* This class is applied to the divs that contain the various "views" or pages of the app. */ +.panel > * { + -webkit-backface-visibility:hidden; + -webkit-perspective:1000; +} + + + +/********************************************************** + TOOL BAR +**********************************************************/ +/* Tool bar appears locked to the bottom of the screen. It is the primary navigation. can contain text or graphical navigation */ +#navbar { + position:absolute; + bottom:0px; + z-index:1000; + text-align:center; + left:0px;right:0px; + height:62px; padding:0px 1px; + background-color:#424D00; + background-image:-webkit-gradient(linear, left top, left bottom, from(#798E19), to(#424D00)); + -webkit-box-shadow:0px 0px 24px rgba(54,37,27,0.4); box-shadow:0px 0px 24px rgba(54,37,27,0.4); +} + +#navbar a { + width:62px; height:62px; + overflow:hidden; + display:inline-block; + font:bold 12px/100px Helvetica, Arial, sans-serif; + color:#E6E2B5; + text-decoration:none; + text-align:center; + text-shadow:0px -1px rgba(54,37,27,.5); + margin:0px auto; + position:relative; +} + +#navbar a:active, #navbar a:focus, #navbar a:active:focus, #navbar .selected { background-color:rgba(218,221,80,.2); } +#navbar .icon:before { + line-height:62px; + text-align:center; + position:absolute; + top:-10px; + left:10px; + font-size:48px; +} +/* jqMobi navbar */ +#navbar a:not(:last-of-type):after { + display:block; + content:""; + width:1px; + height:60px; + float:right; +} + +.jq-badge { + display:-webkit-box; + -webkit-box-direction:reverse; + position:absolute; + font-size:10px; + border:2px solid white; + color:white; + background:red; + line-height:12px; + border-radius:10px; + overflow:hidden; + text-overflow:ellipsis; + min-width:20px; + max-width:90%; + height:20px; + margin:2px; + padding:1px 4px 1px 4px; + top:2px; + right:2px; + text-align:center; + z-index:100; +} + +.jq-badge.br { + bottom:2px; + right:2px; + top:auto; + left:auto; +} + +.jq-badge.bl { + -webkit-box-direction:normal !important; + bottom:2px; + left:2px; + top:auto; + right:auto; +} + +.jq-badge.tl { + -webkit-box-direction:normal !important; + top:2px; + left:2px; + right:auto; + bottom:auto; +} + +.closebutton { + background:url('images/eco/close.png'); + height:23px !important; + width:23px !important; +} + +#jQUi footer { display:none; } /* Custom footers - always hidden */ + +#jQUi nav { display:none; } /* side menu - always hidden */ + +#jQUi > #menu { + z-index:200; + display:none; + width:200px; height:100%; + position:absolute; + bottom:0px; top:0px; left:-200px; + background:#493325; + -webkit-transform:translate3d(0,0,0); + -webkit-transition: all 300ms !important; +} + +#menu_scroller { padding-bottom:10px; } + +#menu > * { + width:90%; + margin-left:auto; margin-right:auto; +} + +.jqMenuHeader { + position:relative; + text-align:left; + margin:4px 0px; + font-size:21px; + font-weight:bold; + text-transform:uppercase; + z-index:1; + color:#E6E2B5; +} + +.jqMenuClose{ + position:absolute; + top:5px; + right:10px; + z-index:2; +} + +#menu .title { + font-size:16px; + margin-bottom:3px; + font-weight:bold; + color:#E6E2B5; +} + +#menu ul { + margin:0px; + padding:0px; + margin-bottom:5px; +} + +#menu ul > li { + padding-left:10px; + display:block; + width:100%; height:28px; + list-style:none; + background:inherit; +} /* A plain, non-interactive list item--best suited to a heading. */ + +#menu ul > li > a { + display:block; + width:100%; height:28px; + font:normal 14px/28px Helvetica, Arial, sans-serif; + color:#E6E2B5; + text-decoration:none; + border-top:1px solid #A48161; + background:#7C543B; +} + +#menu ul > li > a:after { content:""; } +#menu ul > li:last-child > a { border-bottom:1px solid black; } +/* End side menu css */ + +.splashscreen { + background:#E6E2B5; !important; + padding-left:40px; + padding-top:30px !important; + min-height:100%; +} + + +/********************************************************** + UL > LI +**********************************************************/ +/* A touchable, interactive list item. */ +/* The unordered list/list item classes are the basis of the secondary navigation used in JQ.Mobi:the stacked, listed menu system. */ + +ul { + margin:0px; + padding:0px; +} + +ul > li { + display:block; + width:100%; height:48px; + list-style:none; + background:#BB2620; +} /* A plain, non-interactive list item--best suited to a heading. */ + +ul > li > a { + display:block; + width:100%; height:48px; + font:bold 18px/48px Helvetica, Arial, sans-serif; + color:#E6E2B5; + text-decoration:none; + border-bottom:1px solid black; + padding-left:8px; + padding-right:-80px; +} /* A touchable, interactive list item. */ + +ul > li > a:after { + content:">"; + position:absolute; + right:15px; +} + +ul > li > a[selected], ul > li > a:active { + color:#E6E2B5; !important; + background:#DD4037; +} /* A selected and active states for interactive list items. */ + +ul > li.group { + position:relative; + top:-1px; + margin-bottom:-2px; + border-top:1px solid #666; + border-bottom:1px solid #333; + padding:1px 10px; + font-size:17px; + font-weight:bold; + color:#E6E2B5; +} + +ul > li.group:first-child { top:0; } + +h2 { + display:block; + width:100%; height:48px; + font:bold 18px/48px Helvetica, Arial, sans-serif; + color:#E6E2B5; + background:#006B71; + padding:0px 8px 0px 8px; +} /* Header class used for non-navigable header bars (h1 is reserved for the header) */ + +.collapsed:after{content:url("images/eco/collapse.png");position:absolute;top:5px;right:5px;} +.expanded:after{content:url("images/eco/expand.png");position:absolute;top:5px;right:5px;} + + +/********************************************************** + UI +**********************************************************/ +.ui-icon { + background:#666; + background:rgba(0,0,0,.4); + background-repeat:no-repeat; + border-radius:9px; +} + +.ui-loader { display: none; position: absolute; opacity: .85; z-index: 100; left: 50%; width: 200px; margin-left: -100px; margin-top: -35px; padding: 10px 30px; background: #666;background:rgba(0,0,0,.4);border-radius:9px;} + +.ui-loader h1 { + font-size:15px; + text-align:center; +} + +.ui-loader .ui-icon { + position:static; + display:block; + opacity:.9; + margin:0 auto; + width:35px; + height:35px; + background-color:transparent; +} + +.spin { + -webkit-transform:rotate(360deg); + -webkit-animation-name:spin; + -webkit-animation-duration:1s; + -webkit-animation-iteration-count:infinite; +} +@-webkit-keyframes spin { + from { -webkit-transform:rotate(0deg); } + to { -webkit-transform:rotate(360deg); } +} + +.ui-icon-loading { + background-image:url('images/eco/ajax-loader.png'); + width:40px; + height:40px; + border-radius:20px; + background-size:35px 35px; +} + +.ui-corner-all { border-radius:.6em; } +#jQui_mask { position:absolute; top:45%; } + + +/********************************************************** + FORM ELEMENTS +**********************************************************/ +fieldset h4 { + text-align:left; + font:bold 18px/21px Helvetica, Arial, sans-serif; + color:#36251B; + margin-bottom:-8px; margin-top:8px; +} /* Style for subhead for a group of labels/inputs within a fieldset. */ + +fieldset { + margin:12px 8px; + padding:12px 18px 12px 18px; + -webkit-border-radius:5px; border-radius:5px; + outline:0; + border:1px solid #E6E2B5; + width:95%; + background:transparent; +} /* Styles the border line, background image and spacing for fieldsets. */ + +fieldset p { + display:block; + position:relative; + height:37px !important; + overflow:hidden; + padding:8px 0px; + font:normal 14px/10px Helvetica, Arial, sans-serif; + background:transparent; + -webkit-box-shadow:none; +} /* The paragraph within a fieldset is used as a wrapper to help manage the replacement of native input elements. */ + +legend { + font:bold 18px/24px Helvetica, Arial, sans-serif; + color:#36251B; + text-transform:uppercase; + overflow:hidden; + margin:0; +} /* This is the name that appears at the top left of the fieldset. */ + +textarea.jq-ui-forms { + display:block; + margin-top:4px; margin-bottom:18px; + padding:8px; + font:normal 1em/1.5em Helvetica, Arial, sans-serif; + color:#36251B; + border:solid 1px #493325; + outline:0; + background:#E6E2B5; + -webkit-border-radius:6px; border-radius:6px; + -webkit-box-shadow:inset 1px 1px 4px rgba(54,37,27,0.3); box-shadow:inset 1px 1px 4px rgba(54,37,27,0.3); + width:85%; + min-height:150px; + line-height:120%; +} /* These properties determine the look of textarea inputs. */ + +input.jq-ui-forms { + display:inline-block; + width:65%; + margin-top:4px; margin-bottom:12px; + padding:8px; + border:solid 1px #493325; + outline:0; + font:normal 1em/1.5em Helvetica, Arial, sans-serif; + color:#36251B; + background:#E6E2B5; + -webkit-border-radius:6px; border-radius:6px; + -webkit-box-shadow:inset 1px 1px 4px rgba(54,37,27,0.3); box-shadow:inset 1px 1px 4px rgba(54,37,27,0.3); +} /* These properties combine to create the look of the input fields. */ + +input.jq-ui-forms:hover, textarea.jq-ui-forms:hover, input.jq-ui-forms:focus, textarea.jq-ui-forms:focus { border-color:#BB2620; } /* Changes the border color of the input field while active. */ + +input.jq-ui-forms[type=checkbox] ,input.jq-ui-forms[type=radio] { + position:absolute; + left:0; + -webkit-appearance:none; + opacity:0; + margin-bottom:30px; + width:0px; +} /* Blocks rendering of the native radio controls. */ + +input.jq-ui-forms[type=checkbox] + label, input.jq-ui-forms[type=radio] + label { + float:left; + font-size:15px; + font-weight:bold; + line-height:26px; /* changing this value will change the vertical relationship to the radios & checkboxes. */ + margin-left:10px; + padding-left:30px; + background:url('images/eco/custom_inputs.png') 0px 0px no-repeat; + background-repeat:none; + height:25px; +} /* Styling for the labels. */ + +/* Following block of paragraph classes sets up the usage of the custom radio and checkbox graphics. */ +input.jq-ui-forms[type=radio] + label { background-position:0 -225px; } +input.jq-ui-forms[type=checkbox] + label { background-position:0 -25px; } + +/* Styles for "checked." */ +input.jq-ui-forms[type=radio]:checked + label { background-position:0 -300px; } +input.jq-ui-forms[type=radio]:hover:checked + label, +input.jq-ui-forms[type=radio]:focus:checked + label, +input.jq-ui-forms[type=radio]:checked + label:hover, +input.jq-ui-forms[type=radio]:focus:checked + label { background-position:0 -300px; } + +input.jq-ui-forms[type=checkbox]:checked + label { background-position:0 -100px; } +input.jq-ui-forms[type=checkbox]:hover:checked + label, +input.jq-ui-forms[type=checkbox]:focus:checked + label, +input.jq-ui-forms[type=checkbox]:checked + label:hover, +input.jq-ui-forms[type=checkbox]:focus:checked + label { background-position:0 -100px; } + +/* Styles for "hover/focus." */ +input.jq-ui-forms[type=checkbox]:hover + label, +input.jq-ui-forms[type=checkbox]:focus + label, +input.jq-ui-forms[type=checkbox] + label:hover { background-position:0 -0x; } + +input.jq-ui-forms[type=radio]:hover + label, +input.jq-ui-forms[type=radio]:focus + label, +input.jq-ui-forms[type=radio] + label:hover { background-position:0 -200px; } + +/* Styles for "active." */ +input.jq-ui-forms[type=checkbox]:active + label, +input.jq-ui-forms[type=checkbox] + label:hover:active { background-position:0 -0px; } + +input.jq-ui-forms[type=radio]:active + label, +input.jq-ui-forms[type=radio] + label:hover:active { background-position:0 -200px; } + +input.jq-ui-forms[type=checkbox]:active:checked + label, +input.jq-ui-forms[type=checkbox]:checked + label:hover:active { background-position:0 -100px; } + +input.jq-ui-forms[type=radio]:active:checked + label, +input.jq-ui-forms[type=radio]:checked + label:hover:active { background-position:0 -300px; } + +/* Styles for "disabled." */ +input.jq-ui-forms[type=checkbox]:disabled + label, +input.jq-ui-forms[type=checkbox]:hover:disabled + label, +input.jq-ui-forms[type=checkbox]:focus:disabled + label, +input.jq-ui-forms[type=checkbox]:disabled + label:hover, +input.jq-ui-forms[type=checkbox]:disabled + label:hover:active { background-position:0 -175px; opacity:.5 !important; } + +input.jq-ui-forms[type=radio]:disabled + label, +input.jq-ui-forms[type=radio]:hover:disabled + label, +input.jq-ui-forms[type=radio]:focus:disabled + label, +input.jq-ui-forms[type=radio]:disabled + label:hover, +input.jq-ui-forms[type=radio]:disabled + label:hover:active { background-position:0 -250px; opacity:.5 !important; } + +input.jq-ui-forms[type=checkbox]:disabled:checked + label, +input.jq-ui-forms[type=checkbox]:hover:disabled:checked + label, +input.jq-ui-forms[type=checkbox]:focus:disabled:checked + label, +input.jq-ui-forms[type=checkbox]:disabled:checked + label:hover, +input.jq-ui-forms[type=checkbox]:disabled:checked + label:hover:active { background-position:0 -200px; opacity:.5 !important; } + +input.jq-ui-forms[type=radio]:disabled:checked + label, +input.jq-ui-forms[type=radio]:hover:disabled:checked + label, +input.jq-ui-forms[type=radio]:focus:disabled:checked + label, +input.jq-ui-forms[type=radio]:disabled:checked + label:hover, +input.jq-ui-forms[type=radio]:disabled:checked + label:hover:active { background-position:0 -375px; opacity:.5 !important; } + +input.jq-ui-forms[type=radio]:active:checked + label, +input.jq-ui-forms[type=checkbox]:active:checked + label, +input.jq-ui-forms[type=radio]:active + label, +input.jq-ui-forms[type=checkbox]:active + label, +input.jq-ui-slider[type=checkbox]:checked + label , +input.jq-ui-slider[type=radio]:checked + label +{ background-position:-28px 0px; } + +input.jq-ui-slider[type=radio]:disabled:checked + label, +input.jq-ui-slider[type=radio]:focus:disabled:checked + label, +input.jq-ui-slider[type=checkbox]:disabled:checked + label, +input.jq-ui-slider[type=checkbox]:focus:disabled:checked + label +{ background-position:-28px 0px; opacity:.7 !important;} + +/* Styles for "disabled." */ +input.jq-ui-slider[type=checkbox]:disabled + label, +input.jq-ui-slider[type=checkbox]:focus:disabled + label, +input.jq-ui-slider[type=radio]:disabled + label, +input.jq-ui-slider[type=radio]:focus:disabled + label +{ background-position:0 0; opacity:.7 !important; } + +select[disabled]~div { opacity:.7; } + +/* slider controls */ +input.jq-ui-slider { + position:absolute; + left:0; + opacity:0; + -webkit-appearance:none; +} /* Blocks rendering of the native radio controls. */ + +input.jq-ui-slider + label { + float:left; + font-size:15px; + font-weight:normal; + line-height:24px; /* changing this value will change the vertical relationship to the radios & checkboxes. */ + margin-left:10px; + padding-left:57px; + color:#fff; + background:url('images/Off-On_Slider.png') 0 0px no-repeat; + height:24px; + width:57px; + display:inline-block; + -webkit-transition:all 0.3s ease-in-out; +} + +.hasMenu{ +-webkit-transition: all 300ms; + position:absolute !important; + left:0px !important; +} +.hasMenu.on { + position:absolute !important; + left:200px; +} +#menu.on { +left:0px !important; +} + +#badgetablet{ + display:none !important; +} + +@media handheld, only screen and (min-width: 768px){ + .hasMenu, .hasMenu.on { + position:absolute !important; + left:256px !important; + -webkit-transition: all 0ms !important; + } + #badgephone{ display:none !important; } + #badgetablet{ display:inline-block !important; } + .jqMenuClose { display:none !important; } + + nav~#menu { + z-index:200 !important; + width:256px !important; + bottom:0px !important; + height:100% !important; + display:block !important; + position:absolute !important; top:0px !important; left:0px !important; + background:rgba(29,29,28,1) !important; + -webkit-transform:translate3d(0,0,0) !important; + -webkit-transition: all 0ms !important; + } +} + +/** Below are styles for plugins */ +#jq_actionsheet { + position:absolute; + left:0px; + right:0px; + padding-left:10px; + padding-right:10px; + padding-top:10px; + margin:auto; + background:black; + float:left; + z-index:1000; + border-top:#fff 1px solid; + background:rgba(71,71,71,.95); + background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255,255,255,.4)), to(rgba(255,255,255,0)), color-stop(.08,rgba(255,255,255,.1)), color-stop(.08,rgba(255,255,255,0))); + background-image:-webkit-linear-gradient(top, rgba(255,255,255,.4) 0%, rgba(255,255,255,.1) 8%, rgba(255,255,255,0) 8%); + box-shadow:0px -1px 2px rgba(0,0,0,.4); +} +#jq_actionsheet a { + text-decoration:none; + -webkit-transition:all 0.4s ease; + text-shadow:0px -1px rgba(0,0,0,.8); + padding:0px .25em; + border:1px solid rgba(0,0,0,.8); + text-overflow:ellipsis; + -webkit-border-radius:.75em; border-radius:.75em; + background-image:-webkit-gradient(linear, left top, left bottom, from(rgba(255,255,255,.4)), to(rgba(255,255,255,0)), color-stop(.5,rgba(255,255,255,.1)), color-stop(.5,rgba(255,255,255,0))); + background-image:-webkit-linear-gradient(top, rgba(255,255,255,.5) 0%, rgba(255,255,255,.2) 50%, rgba(255,255,255,0) 50%); + -webkit-box-shadow:0px 1px 1px rgba(255,255,255,0.7); box-shadow:0px 1px 1px rgba(255,255,255,0.7); + display:block; + color:white; + text-align:center; + line-height:36px; + font-size:20px; + font-weight:bold; + margin-bottom:10px; + background-color:rgba(130,130,130,1); +} + +#jq_actionsheet a.selected { + background-color:rgba(150,150,150,1); +} + +#jq_actionsheet a.cancel { + background-color:rgba(43,43,43,1); +} + +#jq_actionsheet a.cancel.selected { + background-color:rgba(73,73,73,1); +} +#jq_actionsheet a.red { + color:white; + background-color:rgba(204,0,0,1); +} + +#jq_actionsheet a.red.selected { + background-color:rgba(255,0,0,1); +} + +BODY>DIV#mask{ + display:block; + width:100%; + height:100%; + background:#000; + z-index: 999999; + position:absolute; + top:0; + left:0; +} + +.jqPopup { + display: block; + width: 300px; + border: solid 1px #72767b; + -webkit-box-shadow: 0px 4px 6px #555, 0 0 20px rgba(255,255,255,0.5); + -webkit-border-radius: 10px; + padding: 10px; + opacity: 1; + -webkit-transform: scale(1); + -webkit-transition: all 0.20s ease-in-out; + position: absolute; + z-index: 1000000; + margin-left: auto!important; + margin-right: auto!important; + background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(70,70,70,0.8)), to(rgba(0,20,20,0.8))); +} +.jqPopup >* { +color:white; +} + +.jqPopup.hidden { + opacity: 0; + -webkit-transform: scale(0); + top: 50%; + left: 50%; + margin: 0px auto; +} + +.jqPopup>HEADER{ + font-weight:bold; + font-size:20px; + margin:0; + padding:0; +} + +.jqPopup>DIV{ + font-size:12px; + margin:8px; +} + +.jqPopup>FOOTER{ + width:100%; + text-align:center; + display:block !important; +} + +.jqPopup>FOOTER>A#cancel{ + float:left; +} + +.jqPopup>FOOTER>A#action{ + float:right; + margin-right:4px; +} + +.jqPopup>FOOTER>A.center{ + float:none!important; + width:80%; + margin:8px; +}/** This can be your default scrollbar class. You must use !important to override the default inline styles */ +.scrollBar { + position:absolute !important; + width:5px !important; + height:20px !important; + border-radius:2px !important; + border:1px soldid black !important; + background:black !important; + opacity:0 !important; +}/* + * Since the styles are built in, you have to override values with !important + */ + +/* Row of selected item */ +.jqmobiSelectRowFound { +} + +/* Button (radio) for the found item in the list */ +.jqmobiSelectRowButtonFound{ +} + + +/* Row for items in the list */ +.jqmobiSelectRow{ + +} + +/* button for the items in the list */ +.jqmobiSelectRowButton{ + +} + +/* class for the item text displayed */ +.jqmobiSelectRowText{ +} + +/* Header for select box */ +#jqmobiSelectBoxHeader{ +} + +/* div that holds the options listed*/ +#jqmobiSelectBoxFix { + +} \ No newline at end of file diff --git a/assets/www/css/ui.css b/assets/www/css/ui.css index 47fa224..4052ea5 100644 --- a/assets/www/css/ui.css +++ b/assets/www/css/ui.css @@ -1,4 +1,11 @@ +#backButton { + display:none; +} + +#header > h1 { + z-index: 0; +} #board { display: table; @@ -62,14 +69,4 @@ background-color: #dd6; } -@media only screen and (orientation:landscape) { - #header, #menu { - display: none; - } - - #navbar { - display: block; - } -} - diff --git a/assets/www/index.html b/assets/www/index.html index de4ef16..41db75f 100644 --- a/assets/www/index.html +++ b/assets/www/index.html @@ -13,7 +13,8 @@ - + + @@ -32,6 +33,7 @@ + @@ -39,18 +41,29 @@
-
- +
+ +
+

Open games

+
    +
+
-
+
+ +
@@ -58,7 +71,7 @@
diff --git a/assets/www/js/net.js b/assets/www/js/net.js index cd621b5..8bef20a 100644 --- a/assets/www/js/net.js +++ b/assets/www/js/net.js @@ -1,6 +1,8 @@ var TaflNet = { + user_id: null, + connect: function(address) { if (!address) address = "chrummibei.ch:47413"; @@ -9,13 +11,21 @@ var TaflNet = { this.socket = s; s.on('connect', function() { - s.emit('client.hello'); + if (user_id) { + s.emit('client.hello', {user_id: this.user_id}); + } else { + s.emit('client.hello'); + } }); s.on('server.hello', function(data) { s.emit('game.find_opponent'); }); + s.on('player.id.set', function(data) { + TaflNet.user_id = data.user_id; + }); + s.on('game.start', function(data) { TaflNet.on_game_start(data); }); diff --git a/assets/www/js/tafl.js b/assets/www/js/tafl.js index 6f766fc..0a1bb84 100644 --- a/assets/www/js/tafl.js +++ b/assets/www/js/tafl.js @@ -24,43 +24,50 @@ function TaflState() { this.legal_moves_cache = null; this.winner = null; this.win_reason = null; + + this.move_history = []; // AI related variables this.initial_W = null; this.initial_B = null; - - this.loadBoard = function(board) { - var i, j, pieces; - - this.board = board; - this.N = board.length; - - if (board[0].length !== this.N) throw "BoardNotSquare"; - - pieces = Tafl.count_pieces(board); - this.initial_W = pieces.W; - this.initial_B = pieces.B; - }; - - this.clone = function() { - var i, t = new TaflState(); - - for (i = 0; i < this.N; ++i) { - t.board[i] = this.board[i]; - } - - t.N = this.N; - t.to_move = this.to_move; - t.legal_moves_cache = this.legal_moves_cache; - t.winner = this.winner; - t.win_reason = this.win_reason; - - t.initial_W = this.initial_W; - t.initial_B = this.initial_B; - - return t; - } } +TaflState.prototype.loadBoard = function(board) { + var i, j, pieces; + + this.board = board; + this.N = board.length; + + if (board[0].length !== this.N) throw "BoardNotSquare"; + + pieces = Tafl.count_pieces(board); + this.initial_W = pieces.W; + this.initial_B = pieces.B; +}; +TaflState.prototype.clone = function() { + var i, t = new TaflState(); + + for (i = 0; i < this.N; ++i) { + t.board[i] = this.board[i]; + } + + t.N = this.N; + t.to_move = this.to_move; + t.legal_moves_cache = this.legal_moves_cache; + t.winner = this.winner; + t.win_reason = this.win_reason; + + t.initial_W = this.initial_W; + t.initial_B = this.initial_B; + + t.move_history = []; + for (i = 0; i < this.move_history.length; ++i) { + var move = this.move_history[i]; + + t.move_history.push([ [ move[0][0], move[0][1] ], [ move[1][0], move[1][1] ] ]); + } + + return t; +}; var Tafl = { legal_moves : function(s) { @@ -153,6 +160,8 @@ var Tafl = { else s.to_move = 'W'; + s.move_history.push(move); + s.legal_moves_cache = null; }, diff --git a/assets/www/js/tools.js b/assets/www/js/tools.js new file mode 100644 index 0000000..3fd0cf0 --- /dev/null +++ b/assets/www/js/tools.js @@ -0,0 +1,14 @@ +function generateRandomString(entropy) { + if (! entropy) { entropy = 8; } + + var characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; + var randStr = ''; + for (var i = 0; i < entropy; ++i) { + randStr += characters[Math.floor(Math.random() * characters.length)]; + } + + return randStr; +} + +var exports = {}; +exports.generateRandomString = generateRandomString; diff --git a/assets/www/js/ui.js b/assets/www/js/ui.js index 7f3ee6f..d0965cb 100644 --- a/assets/www/js/ui.js +++ b/assets/www/js/ui.js @@ -2,28 +2,133 @@ //Test //TaflUnitTest(); -/* Initialize and prepare the game */ -var tafl_game = new TaflState(); -tafl_game.loadBoard(Tafl.initialStates.Hnefatafl.board()); -tafl_game.to_move = Tafl.initialStates.Hnefatafl.to_move(); + +var uisettings = { + notification: { + vibrate: true, + vibrate_length: 200, // in ms + beep: true, + beep_times: 1, + }, +}; + +var storage = { + store: function(key, value) { + localStorage.setItem(key, JSON.stringify(value)); + }, + + get: function(key, deflt) { + var value = localStorage.getItem(key); + + return (value !== null)?JSON.parse(value):deflt; + }, + + remove: function(key) { + localStorage.removeItem(key); + }, + + save_game: function(safegame) { + var games = storage.get('open_games', []); + var has_gameid = false; + for (var i = 0; i < games.length; ++i) { + if (games[i].id === safegame.game_id) { + has_gameid = true; + + games[i].turn = safegame.game_state.move_history.length; + + break; + } + } + + if (has_gameid === false) { + games.push({id: safegame.game_id, name: safegame.game_name, turn: safegame.game_state.move_history.length}); + storage.store('open_games', games); + } + + storage.store('safegame_' + safegame.game_id, safegame); + }, +}; + +// Hook up listeners to save game state if we're shut down +document.addEventListener("pause", killNet, false); +document.addEventListener("offline", killNet, false); + /* Create special boardui objects that handles ui related game functions */ var boardui = { moveFrom: null, - my_color: 'W', + my_color: null, activeField: null, active_field: null, - max_width: null, - max_height: null, + game_state: null, + network_game: false, - is_ai: {'W': false, 'B': true }, + game_name: 'Unnamed game', + game_id: null, + + board_size_fixed: false, + + is_ai: {'W': false, 'B': false }, init: function() { - this.max_width = document.getElementById('board').offsetWidth; - this.max_height = document.getElementById('board').offsetHeight; + if (boardui.board_size_fixed === false) { + console.log("Board size fix"); + boardui.fix_board_size(); + boardui.board_size_fixed = true; + } + }, + + load_state: function(game_state) { + if (! game_state) { + game_state = boardui.create_new_state(); + } else { + // Make sure game_state is a TaflState + game_state.__proto__ = TaflState.prototype; + } - return (this.max_width > 0 && this.max_height > 0); + boardui.game_state = game_state; + boardui.fill_board(game_state.board); + }, + + save_game: function() { + if (! boardui.game_id) { + boardui.game_id = generateRandomString(); + } + + var safegame = {}; + safegame.my_color = boardui.my_color; + safegame.game_id = boardui.game_id; + safegame.game_name = boardui.game_name; + safegame.is_ai = boardui.is_ai; + safegame.network_game = boardui.network_game; + + // Get rid of legal_move_cache before storing + boardui.game_state.legal_move_cache = null; + safegame.game_state = boardui.game_state; + + storage.save_game(safegame); + }, + + load_game: function(safegame) { + boardui.my_color = safegame.my_color; + boardui.game_id = safegame.game_id; + boardui.game_name = safegame.game_name; + boardui.is_ai = safegame.is_ai; + boardui.network_game = safegame.network_game; + + boardui.moveFrom = null; + boardui.deactivate(); + + boardui.load_state(safegame.game_state); + }, + + create_new_state: function() { + game_state = new TaflState(); + game_state.loadBoard(Tafl.initialStates.Hnefatafl.board()); + game_state.to_move = Tafl.initialStates.Hnefatafl.to_move(); + + return game_state; }, get_classes: function(type) { @@ -40,6 +145,8 @@ var boardui = { fill_board: function(board) { var N = board.length; + $("#board").empty(); + for (var i = 0; i < N; ++i) { var row = $('
'); @@ -117,8 +224,12 @@ var boardui = { }, square_clicked: function() { - if (tafl_game.to_move !== boardui.my_color) { + if (boardui.game_state === null) { return; } + console.log('game_state'); + + if (boardui.game_state.to_move !== boardui.my_color) { // not my turn + console.log('not my turn'); return; }; @@ -128,10 +239,14 @@ var boardui = { //this.style.border = '2px solid green'; if (boardui.moveFrom === null) { - if (Tafl.is_moving_color(tafl_game, [i, j])) { + console.log('wee'); + + console.log(boardui.game_state.to_move); + if (Tafl.is_moving_color(boardui.game_state, [i, j])) { + console.log("asd"); // Check if there is a move possible from this location - var legal_moves = Tafl.legal_moves(tafl_game); + var legal_moves = Tafl.legal_moves(boardui.game_state); for (var k = 0; k < legal_moves.length; ++k) { if (legal_moves[k][0][0] === i && legal_moves[k][0][1] === j) { @@ -143,6 +258,7 @@ var boardui = { // If this is reached, no possible move from here } + console.log("clock"); } else { if (boardui.moveFrom[0] === i && boardui.moveFrom[1] === j) { // Deactivate active piece boardui.moveFrom = null; @@ -153,7 +269,10 @@ var boardui = { try { var move = [boardui.moveFrom, [i, j]]; boardui.make_move(move); - TaflNet.send_move(move); + + if (boardui.network_game === true) { + TaflNet.send_move(move); + } boardui.moveFrom = null; boardui.deactivate(); @@ -174,22 +293,22 @@ var boardui = { }, make_move: function(move) { - Tafl.make_move(tafl_game, move); + Tafl.make_move(boardui.game_state, move); - this.update_board(tafl_game.board); + this.update_board(boardui.game_state.board); - if (Tafl.terminal_test(tafl_game) === true) { + if (Tafl.terminal_test(boardui.game_state) === true) { // Game ended var win_reason; - switch(tafl_game.win_reason) { + switch(boardui.game_state.win_reason) { case 'NoKing': win_reason = 'The King was captured.'; break; case 'KingEscaped': win_reason = 'The King escaped.'; break; case 'NoLegalMoves': win_reason = 'There are no legal moves.'; break; default: win_reason = 'For a unknown reason'; } - var winner = (tafl_game.winner == 'W')?'White':'Black'; + var winner = (boardui.game_state.winner == 'W')?'White':'Black'; boardui.alert(win_reason + "
" + winner + " player wins."); } else { @@ -199,72 +318,95 @@ var boardui = { }, make_ai_move: function() { - console.log(boardui.is_ai[tafl_game.to_move]); - if (boardui.is_ai[tafl_game.to_move] !== true) { + if (boardui.is_ai[boardui.game_state.to_move] !== true) { return; } // Start new thread for this setTimeout(function() { - var ai_move = TaflAI.get_best_move(tafl_game, 2); + var ai_move = TaflAI.get_best_move(boardui.game_state, 2); boardui.make_move(ai_move.move); + boardui.activate(ai_move.move[1][0], ai_move.move[1][1]); + + boardui.notifiy(); // Notify user of the new ai move }, 0); }, + on_leave_board: function() { + boardui.save_game(); + }, + alert: function(message) { $.ui.popup(message); + + this.notifiy(); + }, + + notifiy: function() { + if (! navigator || ! navigator.notification) { return; } + + if (uisettings.notification.vibrate) { + navigator.notification.vibrate(uisettings.notification.vibrate_length); + } + + if (uisettings.notification.beep) { + navigator.notification.beep(uisettings.notification.beep_times); + } }, rate_current_state: function() { - var score = TaflAI.rate_state(tafl_game); + var score = TaflAI.rate_state(boardui.game_state); $("#score").text(score); }, }; - - -$().ready(function(){ - // Fill board - - var waitForCss = setInterval(function(){ - if (boardui.init()) { - // Game init successful - clearInterval(waitForCss); - - boardui.fix_board_size(); - boardui.fill_board(tafl_game.board); - } - }, 50); - - window.onresize = function() { - setTimeout(boardui.fix_board_size, 50); - }; - - /* - window.onorientationchange = function() { - console.log(window.orientation); - - switch (window.orientation) { - case 90: - case -90: - boardui.fix_board_size(); - break; - default: - boardui.fix_board_size(); - break; - } - }; - */ -}); +var boardUiInitializeBoard = boardui.init; +var boardUiLeaveBoard = boardui.on_leave_board; var game_starter = { + load_game_list: function() { + $.ui.setTitle("Androtafl"); + + console.log("game list"); + + var games = storage.get('open_games', []); + + $("#game_list").empty(); + for (var i = 0; i < games.length; ++i) { + //if (options !== undefined) { + $("#game_list").append('
  • ' + games[i].name + ' (turn ' + games[i].turn + ')
  • '); + //} + } + }, + + load_game: function(game_id) { + console.log("load game"); + + var safegame = storage.get("safegame_" + game_id); + + if (safegame === null) { + return; + } + + boardui.load_game(safegame); + }, + network_game: function() { TaflNet.connect(); document.addEventListener("pause", killNet, false); document.addEventListener("offline", killNet, false); + + $.ui.setTitle("Network game"); + + boardui.game_name = "Network game"; + boardui.network_game = true; + + $("#wait_for_game").css('display','block'); + $("#game_container").css('display','none'); }, game_vs_white_ai: function() { @@ -272,6 +414,11 @@ var game_starter = { boardui.is_ai['B'] = false; boardui.my_color = 'B'; + boardui.game_name = "You VS. CPU"; + + boardui.load_state(); + + $.ui.setTitle("You VS. CPU"); boardui.make_ai_move(); }, @@ -280,12 +427,25 @@ var game_starter = { boardui.is_ai['B'] = true; boardui.my_color = 'W'; + boardui.game_name = "CPU VS. You"; + + boardui.load_state(); + + $.ui.setTitle("CPU VS. You"); boardui.make_ai_move(); }, }; +var gameStarterLoadGameList = game_starter.load_game_list; // To be accessed via data-load TaflNet.on_game_start = function(data) { + $("#wait_for_game").css('display','none'); + $("#game_container").css('display','block'); + boardui.my_color = data.your_color; + + boardui.game_id = data.game_id; + boardui.load_state(); + var colorName = (data.your_color == 'W')?'White':'Black'; boardui.alert("Game started. You're " + colorName); @@ -294,6 +454,8 @@ TaflNet.on_game_start = function(data) { TaflNet.on_move = function(move) { boardui.make_move(move); boardui.activate(move[1][0], move[1][1]); + + boardui.notifiy(); // Notify the user of the opponents move }; diff --git a/server/tafl_server.js b/server/tafl_server.js index 80a5714..1bacea4 100644 --- a/server/tafl_server.js +++ b/server/tafl_server.js @@ -3,6 +3,7 @@ // TODO: Rewrite the initialisation to be used behind apache var io = require('socket.io').listen(47413); var tafllib = require('../assets/www/js/tafl'); +var tools = require('../assets/www/js/tools'); var tafl = tafllib.tafl; var games = {}; @@ -11,18 +12,6 @@ var sockets_waiting = []; var ready = function(socket) { socket.emit('ready'); }; -function generateRandomString(entropy) { - if (! entropy) { entropy = 8; } - - var characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; - var randStr = ''; - for (var i = 0; i < entropy; ++i) { - randStr += characters[Math.floor(Math.random() * characters.length)]; - } - - return randStr; -} - io.sockets.on('connection', function(socket) { socket.emit('server.hello'); @@ -37,11 +26,13 @@ io.sockets.on('connection', function(socket) { socket.on('client.hello', function(user_id) { // generate user-id - if (! user_id) + if (! user_id) { user_id = generateRandomString(16); + socket.emit('player.id.set', user_id); + } + socket.set('player.id', user_id); - socket.emit('player.id.set', user_id); players[user_id] = socket; @@ -61,9 +52,11 @@ io.sockets.on('connection', function(socket) { socket.opponent = socket2; socket2.opponent = socket; + + var game_id = generateRandomString(); - socket2.emit('game.start', {your_color: 'W'}); - socket.emit('game.start', {your_color: 'B'}); + socket2.emit('game.start', {your_color: 'W', board_id: game_id }); + socket.emit('game.start', {your_color: 'B', board_id: game_id }); }); socket.on('board.get', function() {