Compare commits

...

6 commits

8 changed files with 252 additions and 91 deletions

View file

@ -1,8 +1,8 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<widget id="org.adrianvictor.impostor" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0"> <widget id="org.adrianvictor.impostor" version="1.1.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>Impostor</name> <name>Impostor</name>
<description> <description>
A game of word guessing where an impostor is trying to get along. A game of guessing where everyone but the impostor knows the word.
</description> </description>
<author email="adrianvictor@disroot.org" href="https://adrianvic.github.io"> <author email="adrianvictor@disroot.org" href="https://adrianvic.github.io">
Adrian Victor Adrian Victor
@ -17,11 +17,6 @@
<icon src="res/icon/android/hdpi.png" density="hdpi" /> <icon src="res/icon/android/hdpi.png" density="hdpi" />
<icon src="res/icon/android/mdpi.png" density="mdpi" /> <icon src="res/icon/android/mdpi.png" density="mdpi" />
<icon src="res/icon/android/ldpi.png" density="ldpi" /> <icon src="res/icon/android/ldpi.png" density="ldpi" />
<splash src="res/screen/android/splash-port-ldpi.png" density="port-ldpi" /> <preference name="SplashScreen" value="none"/>
<splash src="res/screen/android/splash-port-mdpi.png" density="port-mdpi" />
<splash src="res/screen/android/splash-port-hdpi.png" density="port-hdpi" />
<splash src="res/screen/android/splash-port-xhdpi.png" density="port-xhdpi" />
<splash src="res/screen/android/splash-port-xxhdpi.png" density="port-xxhdpi" />
<splash src="res/screen/android/splash-land-ldpi.png" density="land-ldpi" />
</platform> </platform>
</widget> </widget>

1
docs Symbolic link
View file

@ -0,0 +1 @@
www

24
package-lock.json generated
View file

@ -9,7 +9,9 @@
"version": "1.0.0", "version": "1.0.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"devDependencies": { "devDependencies": {
"cordova-android": "^15.0.0" "cordova-android": "^15.0.0",
"cordova-android-stayawake": "github:rootzoll/cordova-android-stayawake",
"cordova-plugin-vibration": "^3.1.1"
} }
}, },
"node_modules/@netflix/nerror": { "node_modules/@netflix/nerror": {
@ -190,6 +192,12 @@
"node": ">=20.17.0 || >=22.9.0" "node": ">=20.17.0 || >=22.9.0"
} }
}, },
"node_modules/cordova-android-stayawake": {
"version": "0.0.1",
"resolved": "git+ssh://git@github.com/rootzoll/cordova-android-stayawake.git#19e13d80ed9870b7e833de1ff3049ee477179491",
"dev": true,
"license": "Apache 2.0"
},
"node_modules/cordova-common": { "node_modules/cordova-common": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/cordova-common/-/cordova-common-6.0.0.tgz", "resolved": "https://registry.npmjs.org/cordova-common/-/cordova-common-6.0.0.tgz",
@ -209,6 +217,20 @@
"node": ">=20.9.0" "node": ">=20.9.0"
} }
}, },
"node_modules/cordova-plugin-vibration": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/cordova-plugin-vibration/-/cordova-plugin-vibration-3.1.1.tgz",
"integrity": "sha512-qgv67Rueo4Pydfant3TwnXeFiN9dl+6lKMM6h5jYg9XewiGAGOr8vfWsTvQssC3m3xMKGS1ap3xPNH+BzZ4RMA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"cordovaDependencies": {
"4.0.0": {
"cordova": ">100"
}
}
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",

View file

@ -1,7 +1,7 @@
{ {
"name": "org.adrianvictor.impostor", "name": "org.adrianvictor.impostor",
"displayName": "Impostor", "displayName": "Impostor",
"version": "1.0.0", "version": "1.1.0",
"description": "A game of word guessing where an impostor is trying to get along.", "description": "A game of word guessing where an impostor is trying to get along.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@ -15,9 +15,15 @@
"cordova": { "cordova": {
"platforms": [ "platforms": [
"android" "android"
] ],
"plugins": {
"cordova-plugin-vibration": {},
"cordova-android-stayawake": {}
}
}, },
"devDependencies": { "devDependencies": {
"cordova-android": "^15.0.0" "cordova-android": "^15.0.0",
"cordova-android-stayawake": "github:rootzoll/cordova-android-stayawake",
"cordova-plugin-vibration": "^3.1.1"
} }
} }

4
res/colors.xml Normal file
View file

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<color name="cdv_splashscreen_background">#000000</color>
</resources>

View file

@ -7,6 +7,7 @@
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="script.js" defer></script> <script src="script.js" defer></script>
<script type="text/javascript" src="cordova.js"></script> <script type="text/javascript" src="cordova.js"></script>
<script src="StayAwake.js"></script>
</head> </head>
<body> <body>
<header> <header>
@ -15,6 +16,17 @@
</header> </header>
<div id="playlistHolder"> <div id="playlistHolder">
<div id="hintCheckHolder" class="optionsHolder">
<p>Dicas</p>
<input type="checkbox" id="hintCheck" checked>
</div>
<div id="timerInputHolder" class="optionsHolder">
<p>Temporizador (minutos)</p>
<input type="number" max="" id="timerInput" value="3" >
</div>
<div class="optionsHolder">
<p>Jogadores</p>
<div id="playersList"> <div id="playersList">
<div class="playerHolder"> <div class="playerHolder">
<input type="text" placeholder="Nome do primeiro jogador"/> <input type="text" placeholder="Nome do primeiro jogador"/>
@ -27,9 +39,6 @@
</div> </div>
</div> </div>
<button id="newPlayerButton">Adicionar jogador</button> <button id="newPlayerButton">Adicionar jogador</button>
<div id="hintCheckHolder">
<p>Dicas</p>
<input type="checkbox" id="hintCheck" checked>
</div> </div>
</div> </div>
@ -38,6 +47,7 @@
</div> </div>
<div id="bottomBar"> <div id="bottomBar">
<button id="changeTheme" onclick="changeTheme()">Mudar tema</button>
<button id="startGame">Próximo</button> <button id="startGame">Próximo</button>
</div> </div>
</body> </body>

View file

@ -4,6 +4,15 @@ const startButton = document.querySelector("#startGame");
const logArea = document.querySelector("#gameLog"); const logArea = document.querySelector("#gameLog");
const body = document.querySelector("body"); const body = document.querySelector("body");
const hintCheckbox = document.querySelector("#hintCheck"); const hintCheckbox = document.querySelector("#hintCheck");
const timerInput = document.getElementById("timerInput");
const isCordova = typeof cordova !== 'undefined';
const theme = localStorage.getItem('theme');
if (theme == "light") {
body.classList.add("light-theme");
}
let stage = 0; let stage = 0;
let currentPlayer = 0; let currentPlayer = 0;
@ -12,6 +21,7 @@ let allPlayers;
let currentName; let currentName;
let secret; let secret;
let enableHint; let enableHint;
let timer;
nPlayerButton.addEventListener('click', () => { nPlayerButton.addEventListener('click', () => {
const holder = document.createElement("div"); const holder = document.createElement("div");
@ -66,18 +76,32 @@ startButton.addEventListener('click', () => {
case -1: case -1:
currentName = allPlayers[currentPlayer].value; currentName = allPlayers[currentPlayer].value;
clear(` clear(`
<p>Entregue o dispositivo para:<br><span class="playerName">${currentName}</span><br>para continuar.</p> <p>Entregue o dispositivo para:<br><span class="playerName">${currentName}</span><br>para continuar</p>
`); `);
stage = 1; stage = 1;
break; break;
case 2: { case 2: {
clear("O jogo começou! Cada um deve falar uma palavra relacionada ao tema.\nProssiga após a votação."); clear("O jogo começou! Cada um deve falar uma palavra relacionada ao tema.\n<p id='timer'>Prepare-se!</p>");
startTimer(timerInput.value);
stage += 1;
break;
}
case 3: {
if (isCordova && !!navigator.vibrate) {
navigator.vibrate(0);
}
if (isCordova && !!StayAwake.enableScreenTimeout) {
StayAwake.enableScreenTimeout();
}
clearInterval(timer);
clear("O jogo acabou! Votem para expulsar um jogador.")
stage += 1; stage += 1;
break; break;
} }
case 3: { case 4: {
clear(`O impostor era <b>${impostor}</b>; a palavra era <b>${secret.word}</b>; a dica <b>${secret.hint}</b>.`); clear(`O impostor era <b>${impostor}</b>; a palavra era <b>${secret.word}</b>; a dica <b>${secret.hint}</b>.`);
stage += 1; stage += 1;
break; break;
@ -89,29 +113,29 @@ startButton.addEventListener('click', () => {
currentPlayer = 0; currentPlayer = 0;
break; break;
} }
}); });
function choose() { function choose() {
const randomIndex = Math.floor(Math.random() * allPlayers.length); const randomIndex = Math.floor(Math.random() * allPlayers.length);
return allPlayers[randomIndex].value; return allPlayers[randomIndex].value;
} }
function log(text) { function log(text) {
logArea.innerHTML = logArea.innerHtml + "<br>" + text; logArea.innerHTML = logArea.innerHtml + "<br>" + text;
} }
function clear(text) { function clear(text) {
logArea.classList.forEach(item => { logArea.classList.forEach(item => {
logArea.classList.remove(item); logArea.classList.remove(item);
}) })
logArea.innerHTML = text; logArea.innerHTML = text;
} }
function logClass(className) { function logClass(className) {
logArea.classList.toggle(className); logArea.classList.toggle(className);
} }
async function getWordWithHint() { async function getWordWithHint() {
try { try {
const response = await fetch('words.json'); const response = await fetch('words.json');
const data = await response.json(); const data = await response.json();
@ -125,4 +149,43 @@ async function getWordWithHint() {
} catch (error) { } catch (error) {
console.error('Error loading words:', error); console.error('Error loading words:', error);
} }
} }
function startTimer(minutes) {
const now = new Date();
const minutesLater = new Date(now.getTime() + minutes * 60 * 1000);
timer = setInterval(() => {
const timerElement = document.getElementById("timer");
if (!timerElement) { return; }
const rightNow = new Date().getTime();
var distance = minutesLater.getTime() - rightNow;
var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((distance % (1000 * 60)) / 1000);
timerElement.innerText = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
if (isCordova && !!StayAwake.disableScreenTimeout) {
StayAwake.disableScreenTimeout();
}
if (distance < 0) {
clearInterval(timer);
if (!!navigator.vibrate) {
navigator.vibrate([1000, 500, 1000, 2000, 1000, 500, 1000, 2000, 1000, 500, 1000, 2000]);
}
if (isCordova && !!StayAwake.enableScreenTimeout) {
StayAwake.enableScreenTimeout();
}
timerElement.innerText = "Acabou!";
}
}, 1000)
}
function changeTheme(){
body.classList.toggle('light-theme');
console.log(body.classList.contains('light-theme') ? "light" : "dark")
localStorage.setItem('theme', body.classList.contains('light-theme') ? "light" : "dark");
}

View file

@ -1,5 +1,6 @@
:root { :root {
--background: black; --background: black;
--backgroundLighter: rgb(20, 20, 20);
--foreground: white; --foreground: white;
--saBORbackground: rgba(54, 54, 54, 0.5); --saBORbackground: rgba(54, 54, 54, 0.5);
--saBORforeground: darkgrey; --saBORforeground: darkgrey;
@ -7,10 +8,24 @@
--civilColor: rgb(0, 50, 0); --civilColor: rgb(0, 50, 0);
} }
.light-theme {
--background: white;
--backgroundLighter: rgb(230, 230, 230);
--foreground: black;
--saBORbackground: rgba(200, 200, 200, 0.5);
--saBORforeground: darkgrey;
--impostorColor: rgb(200, 120, 120);
--civilColor: rgb(120, 200, 120);
}
* { * {
transition: .2s;
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
scrollbar-color: var(--foreground) transparent;
scrollbar-width: thin;
user-select: none;
} }
body { body {
@ -23,8 +38,11 @@ body {
padding: 2em; padding: 2em;
gap: 1em; gap: 1em;
height: 100vh; height: 100vh;
max-height: 100vh;
width: 30vw; width: 30vw;
margin: auto; margin: auto;
overflow: hidden;
scroll-behavior: smooth;
} }
header { header {
@ -92,19 +110,21 @@ button {
} }
/* button:hover { /* button:hover {
background-color: var(--foreground); background-color: var(--foreground);
color: var(--background); color: var(--background);
} */ } */
.playerHolder { .playerHolder {
margin-bottom: 1em; margin-bottom: 1em;
display: flex; display: flex;
gap: .6em; gap: .6em;
overflow-y: auto;
overflow-x: hidden;
button { button {
background-color: transparent; background-color: transparent;
font-family: monospace; font-family: monospace;
color: white; color: var(--foreground);
text-shadow: none; text-shadow: none;
} }
} }
@ -115,12 +135,11 @@ input:focus {
#newPlayerButton { #newPlayerButton {
width: 100%; width: 100%;
margin-bottom: 1em;
} }
#startGame { #startGame {
margin-top: auto; /* margin-top: auto;
margin-left: auto; margin-left: auto; */
} }
#gameLog { #gameLog {
@ -128,6 +147,7 @@ input:focus {
margin: auto 0; margin: auto 0;
text-align: center; text-align: center;
transition: .4s; transition: .4s;
border-width: medium 0;
p { p {
margin: 0; margin: 0;
@ -142,6 +162,17 @@ input:focus {
background-color: var(--civilColor); background-color: var(--civilColor);
} }
#playlistHolder {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding-right: 0.5em;
padding: 1em .4em 0 .4em;
background-color: var(--backgroundLighter);
border-top: medium solid var(--saBORbackground);
border-radius: 0 0 10px 10px;
}
body.game #playlistHolder { body.game #playlistHolder {
display: none; display: none;
} }
@ -150,14 +181,20 @@ body.game #gameLog {
display: unset; display: unset;
} }
body.game header {
display: none;
}
#bottomBar { #bottomBar {
margin-top: auto;
display: flex; display: flex;
gap: 1em; gap: .6em;
flex-shrink: 0;
margin-top: 0;
justify-content: end;
} }
#hintCheckHolder p { #hintCheckHolder p {
margin: auto 0 auto 0; margin: auto auto auto 0;
} }
#hintCheckHolder { #hintCheckHolder {
@ -165,11 +202,34 @@ body.game #gameLog {
gap: .4em; gap: .4em;
} }
#timerInput {
width: 100%;
}
.optionsHolder {
border-bottom: medium solid var(--saBORbackground);
margin-bottom: 1em;
border-radius: 5px;
padding: 0 .8em 1em;
p {
margin-bottom: .4em;
}
}
.optionsHolder:last-child {
border-bottom: none;
}
.playerName { .playerName {
font-size: x-large; font-size: x-large;
font-weight: bolder; font-weight: bolder;
} }
#timer {
font-size: xx-large;
}
.rainbowText { .rainbowText {
background: linear-gradient(to right, var(--foreground), var(--background), var(--foreground), var(--impostorColor), var(--foreground), var(--civilColor)); -webkit-background-clip: text; background: linear-gradient(to right, var(--foreground), var(--background), var(--foreground), var(--impostorColor), var(--foreground), var(--civilColor)); -webkit-background-clip: text;
background-clip: text; background-clip: text;
@ -197,6 +257,6 @@ body.game #gameLog {
@media (max-width: 720px) { @media (max-width: 720px) {
body { body {
width: 96vw; width: 100vw;
} }
} }