Major change

1. seperated view
2. use object data to communicate with room
3. changed main view and room view
This commit is contained in:
Eunchong Kim 2021-08-01 13:43:06 +09:00
parent 0d31d4e570
commit e45bc45ff4
10 changed files with 619 additions and 376 deletions

View File

@ -1,8 +1,7 @@
// index.js
/* Class */
import BasicCanvas from './js/basic_canvas.js';
import Room from './js/room.js';
import View from './js/view.js';
/* SCSS */
import './styles/uno-game.scss';
/* Image */
@ -16,84 +15,18 @@ global.uno_game_div;
global.uno_game_w = window.innerWidth-1;
global.uno_game_h = window.innerHeight-1;
const view = new View();
/* Main view */
document.addEventListener('DOMContentLoaded', () => {
// Get uno-game-div
console.log('UNO GAME')
// Get uno-game div
global.uno_game_div = document.getElementById('uno-game');
global.uno_game_div.classList.add('uno-game-div');
// Create background canv
const bkg = new BasicCanvas(0, 0, global.uno_game_w, global.uno_game_h);
bkg.canvas.style.backgroundImage = 'url(' + green_table +')';
// Create UNO title
const title = document.createElement('h1');
title.innerHTML = 'UNO Game';
title.classList.add('uno-game-title');
title.style.fontSize = global.uno_game_h/6 + 'px';
title.style.top = global.uno_game_h/3 + 'px';
title.style.zIndex = global.canvas_count;
global.canvas_count++;
global.uno_game_div.appendChild( title );
// Create div for button group
const btn_group = document.createElement('div');
btn_group.classList.add('uno-game-btn-div');
btn_group.style.top = global.uno_game_h*2/3 + 'px';
btn_group.style.zIndex = global.canvas_count;
global.canvas_count++;
global.uno_game_div.appendChild( btn_group );
// Create single player button
const sp_btn = document.createElement('button');
sp_btn.classList.add('uno-game-btn');
sp_btn.innerHTML = 'Single Player';
sp_btn.style.fontSize = global.uno_game_h/16 + 'px';
btn_group.appendChild( sp_btn );
// Create multi player button
const mp_btn = document.createElement('button');
mp_btn.classList.add('uno-game-btn');
mp_btn.innerHTML = 'Multi Player (not work)';
mp_btn.style.fontSize = global.uno_game_h/16 + 'px';
btn_group.appendChild( mp_btn );
// Add event
sp_btn.addEventListener("click", e => {
// Remove elements
title.remove();
sp_btn.remove();
btn_group.remove();
createRoom();
});
global.uno_game_div.classList.add('uno-game-main-div');
global.uno_game_div.style.height = global.uno_game_h +'px';
// Set background to div
global.uno_game_div.style.backgroundImage = 'url(' + green_table +')';
view.showMainMenu();
});
/* Game start */
async function createRoom() {
console.info('Game start');
const room = new Room('room1');
room.addHuman('newini');
room.addBot();
room.addBot();
room.addBot();
await( room.initCards() );
await new Promise(resolve => setTimeout(resolve, 1000));
await( room.dealCards() );
await new Promise(resolve => setTimeout(resolve, 500));
await( room.startGame() );
}
console.log('Uno End')

View File

@ -1,23 +0,0 @@
import Player from './player.js';
export default class Bot extends Player {
constructor(name, id) {
super(name+id, id);
this._type = 'bot';
}
changeColor() {
return Math.floor( Math.random() * 4 );
}
playCard(top_card) {
for (let i=0; i<this._cards.length; i++) {
const card = this._cards[i];
if ( top_card.isMatch(card) ) {
this._cards.splice(i, 1)[0];
return card;
}
}
return null;
}
}

View File

@ -31,17 +31,26 @@ export default class Card extends BasicCanvas {
});
}
set num(num) {
this._num = num;
}
get num() {
return this._num;
}
set num(num) {
this._num = num;
set color_n(color_n) {
this._color_n = color_n;
}
get color_n() {
return this._color_n;
}
set color_n(color_n) {
this._color_n = color_n;
set data(data) {
this._num = data.num;
this._color_n = data.color_n;
}
get data() {
return {num: this._num, color_n: this._color_n};
}
isMatch(card) {

View File

@ -1,16 +0,0 @@
import Player from './player.js';
import BasicCanvas from './basic_canvas.js';
export default class Human extends Player {
constructor(name, id) {
super(name, id);
this._type = 'human';
}
playCard(top_card) {
return this._cards.splice(0, 1)[0];
}
}

View File

@ -1,81 +0,0 @@
import BasicCanvas from './basic_canvas.js';
export default class Player extends BasicCanvas {
constructor(name, id) {
super();
this._name = name;
this._id = id;
this._cards = [];
this._type = '';
}
get id() {
return this._id;
}
set id(id) {
this._id = id;
}
get name() {
return this._name;
}
set name(name) {
this._name = name;
}
get type() {
return this._type;
}
set type(type) {
this._type = type;
}
get cards() {
return this._cards;
}
set cards(cards) {
this._cards = cards;
}
addCard(card) {
this._cards.push(card);
this.sortCards();
if (this.type === 'human') {
card.drawImageFront();
}
}
removeCard(card) {
this._cards.splice(this._cards.indexOf(card), 1);
}
isEmpty() {
return (this._cards.length === 0);
}
ellipticalFormula(x, a, b) {
return b * ( 1 - Math.sqrt( 1 - (x/a - 1)**2 ) );
}
reDeployCards() {
for (let i=0; i<this._cards.length; i++) {
if (this._id === 0) {
const x0 = global.uno_game_w/4;
const dx = global.uno_game_w/2 / (this._cards.length+1);
const y0 = global.uno_game_h*4/5;
this._cards[i].move(x0 + dx*(i+1), y0);
} else {
const x0 = global.uno_game_w*(this._id-1)/3;
const dx = global.uno_game_w/3 / (this._cards.length+2);
this._cards[i].move(x0 + dx*(i+1), this.ellipticalFormula(x0 + dx*(i+1), global.uno_game_w/2, global.uno_game_h));
}
}
}
sortCards() {
this._cards.sort( (a, b) => (a.num > b.num) ? 1 : -1 );
this.reDeployCards();
}
}

View File

@ -1,110 +1,135 @@
import BasicCanvas from './basic_canvas.js';
import Human from './human.js';
import Bot from './bot.js';
import Card from './card.js';
/* Images */
import Rule from './rule.js';
export default class Room extends BasicCanvas {
export default class Room {
constructor(name) {
super(0, 0, global.uno_game_w, global.uno_game_h/10);
this._rule = new Rule();
this._name = name;
this._players = [];
this._cards = [];
this._used_cards = [];
this._remain_cards = [];
this._discard_piles = [];
this._discard_pile_top = {};
this._current_player = {};
this._game_status = 'ready';
}
// Fill room name
this._ctx.font = Math.floor(this._h/3) + "px Arial";
this._ctx.fillText(name, 10, 50);
receive(data) {
if (data.ctrl === 'addHuman') this.addHuman(data.name);
else if (data.ctrl === 'addBot') this.addBot();
else if (data.ctrl === 'startGame') this.startGame();
else if (data.ctrl === 'playCard') this.playCard(data.card);
else if (data.ctrl === 'changeColor') this.changeColor(data.color_n);
else if (data.ctrl === 'drawCard') this.drawCard();
}
addHuman(name) {
this._players.push( new Human(name, this._players.length) );
this._players.push( {type: 'human', name: name, id: this._players.length } );
console.log(this._players);
}
addBot() {
this._players.push( new Bot('bot', this._players.length) );
this._players.push( {type: 'bot', name: 'bot'+this._players.length, id: this._players.length} );
console.log(this._players);
}
initCards() {
console.log('Initialize cards')
const index_arr = [...Array(108).keys()];
for (let num=0; num<14; num++) {
for (let color_n=0; color_n<8; color_n++) {
if ( (num === 0) && (color_n >= 4) ) { // Skip blank card
continue;
}
if ( (num === 13) && (color_n >= 4) ) { // +4 cards
num = 14;
}
const card_index = index_arr.splice(Math.floor( Math.random() * index_arr.length), 1)[0];
this._cards[ card_index ] = new Card(global.uno_game_w*6/16+card_index, global.uno_game_h/2, num, color_n%4);
}
}
// re-order z-index
for (let i=0; i<this._cards.length; i++) {
this._cards[i].refresh();
}
}
dealCards() {
console.log('Deal cards')
this._players.forEach( (player) => {
for (let i=0; i<7; i++) {
player.addCard( this._cards.pop() );
}
});
respondData() {
return JSON.stringify( {
room: {
name: this._name
},
players: this._players,
remain_cards: this._remain_cards,
discard_piles: this._discard_piles,
discard_pile_top: this._discard_pile_top,
current_player: this._current_player,
game_status: this._game_status
} );
}
async startGame() {
console.log('Game start');
console.log('[Room] Game start');
// Init
this._turn_count = 0;
this._game_status = 'init';
this._skip = false;
this._reverse = false;
this._draw2 = false;
this._draw4 = false;
this._current_player = this._players[0];
this._wild = false;
this._turn_count = 0,
await( this.initDiscardPile() );
await ( this.initCards() );
await new Promise(resolve => setTimeout(resolve, 100));
await ( this.dealCards() );
await new Promise(resolve => setTimeout(resolve, 100));
await ( this.initDiscardPile() );
await new Promise(resolve => setTimeout(resolve, 500));
this._current_player = this._players[0];
await new Promise(resolve => setTimeout(resolve, 500));
this._game_status = 'start';
this.initTurn();
}
initCards() {
console.log('[Room] Initialize cards')
const index_arr = [...Array(108).keys()]; // Create index pool
for (let x=0; x<14; x++) {
let num = x;
for (let y=0; y<8; y++) {
const color_n = y%4;
if ( (x === 0) && (y >= 4) ) continue; // Skip blank card
if ( (x === 13) && (y >= 4) ) num = 14; // +4 cards
// Get random index
const card_index = index_arr.splice(Math.floor( Math.random() * index_arr.length), 1)[0];
this._remain_cards[ card_index ] = {num: num, color_n: color_n, x: x, y: y};
}
}
}
dealCards(n_deal_cards=7) {
console.log('[Room] Deal cards')
this._players.forEach( (player) => {
player.cards = [];
for (let i=0; i<n_deal_cards; i++) player.cards.push( this._remain_cards.pop() );
player.cards.sort( (a, b) => (a.num > b.num) ? 1 : -1 );
});
}
// Not allow special card to be first discard pile
initDiscardPile() {
while (true) {
const card = this._cards.pop();
const card = this._remain_cards.pop();
if (card.num <= 9) {
this.changeTopCard( card );
break;
} else {
this._cards.splice( Math.floor(Math.random()*this._cards.length), 0, card);
// push back to a random position
this._remain_cards.splice( Math.floor(Math.random()*this._remain_cards.length), 0, card);
}
}
}
async initTurn() {
console.log('Turn count: ' + this._turn_count + ', current player: ' + this._current_player.name);
console.log('[Room] Turn count: ' + this._turn_count + ', current player: ' + this._current_player.name);
await new Promise(resolve => setTimeout(resolve, 500));
// check draw cards
if (this._draw2) {
this._draw2 = false;
for (let i=0; i<2; i++) this._current_player.addCard( this._cards.pop() );
for (let i=0; i<2; i++) this._current_player.cards.push( this._remain_cards.pop() );
this._current_player.cards.sort( (a, b) => (a.num > b.num) ? 1 : -1 );
this.finishTurn();
} else if (this._draw4) {
this._draw4 = false;
for (let i=0; i<4; i++) this._current_player.addCard( this._cards.pop() );
for (let i=0; i<4; i++) this._current_player.cards.push( this._remain_cards.pop() );
this._current_player.cards.sort( (a, b) => (a.num > b.num) ? 1 : -1 );
this.finishTurn();
} else {
if (this._current_player.type === 'bot') {
this.botTurn();
} else {
this.humanTurn();
}
}
}
@ -112,88 +137,61 @@ export default class Room extends BasicCanvas {
async botTurn() {
await new Promise(resolve => setTimeout(resolve, 500));
const card = await( this._current_player.playCard(this._top_discard_pile) );
if (card) {
console.log('played card num: ' + card.num + ', color: ' + card.color_n);
this.changeTopCard(card);
} else {
const card = this._cards.pop();
console.log('drawed card num: ' + card.num + ', color: ' + card.color_n);
this._current_player.addCard(card);
}
const card = await ( this.botChooseCard() );
if (card) {
this.playCard(card);
} else {
this.drawCard();
}
}
botChooseCard() {
for (let i=0; i<this._current_player.cards.length; i++) {
const card = this._current_player.cards[i];
if ( this._rule.checkCardsMatch(this._discard_pile_top, card) ) {
if (card.num >= 13) { // Change color
this.changeColor( Math.floor( Math.random() * 4 ) );
}
return card;
}
}
return null;
}
playCard(card) {
console.log('[Room] played card num: ' + card.num + ', color: ' + card.color_n);
const player = this._current_player;
// Remove card from player cards
player.cards.splice(player.cards.findIndex(c => (c.x === card.x && c.y === card.y)), 1);
this.changeTopCard(card);
this.finishTurn();
}
humanTurn() {
this._top_draw_card = this._cards[ this._cards.length-1 ];
this._top_draw_card.mouseEffect();
changeColor(color_n) {
this._wild = true;
this._wild_color_n = color_n;
}
// Select card event
this._current_player.cards.forEach( (card) => {
if (this._top_discard_pile.isMatch(card)) {
card.mouseEffect();
drawCard() {
const card = this._remain_cards.pop();
console.log('[Room] drawed card num: ' + card.num + ', color: ' + card.color_n);
card.canvas.addEventListener('click', () => {
console.log('played card num: ' + card.num + ', color: ' + card.color_n);
this._current_player.removeCard(card);
// Remove event listener
this._top_draw_card.resetEventListener();
this._current_player.cards.forEach( (card) => {
card.resetEventListener();
});
// Show color change blocks
if (card.num === 13 || card.num === 14) {
const bc_colors = [];
for (let i=0; i<4; i++) {
const w = global.uno_game_w;
const bc = new BasicCanvas(w/2+w*i/16, global.uno_game_h*3/4, w/16, w/16);
bc.fillColor(i);
bc.canvas.addEventListener('click', () => {
bc_colors.forEach( bc_color => bc_color.remove() );
// Change card color
card.color_n = i;
// Process
this.changeTopCard(card);
this.finishTurn();
});
bc_colors.push(bc);
}
} else {
this.changeTopCard(card);
this.finishTurn();
}
});
}
});
// Draw card event
this._top_draw_card.canvas.addEventListener('click', () => {
const card = this._cards.pop();
console.log('drawed card num: ' + card.num + ', color: ' + card.color_n);
// Remove event listener
this._top_draw_card.resetEventListener();
this._current_player.cards.forEach( (card) => {
card.resetEventListener();
});
this._current_player.addCard(card);
this.finishTurn();
});
this._current_player.cards.push(card);
this._current_player.cards.sort( (a, b) => (a.num > b.num) ? 1 : -1 );
this.finishTurn();
}
async finishTurn() {
console.log('finish turn')
console.log('[Room] finish turn')
// re-deploy player's cards
await( this._current_player.reDeployCards() );
await new Promise(resolve => setTimeout(resolve, 500));
// Check empty
if (this._current_player.isEmpty()) {
console.log('player: ' + this._current_player.name + ' has no card left. Game end');
if (this._current_player.cards.length === 0) {
console.log('[Room] player: ' + this._current_player.name + ' has no card left. Game end');
this._game_status = 'finish';
} else {
this._turn_count++;
await ( this.decideNextPlayer() );
@ -221,15 +219,18 @@ export default class Room extends BasicCanvas {
}
changeTopCard(card) {
if (this._top_discard_pile) this._used_cards.push(this._top_discard_pile);
this._top_discard_pile = card;
this._top_discard_pile.drawImageFront(global.uno_game_w*8/16+this._turn_count, global.uno_game_h/2);
this._top_discard_pile.refresh();
console.log('[Room] change top card num: ' + card.num + ', color_n: ' + card.color_n)
if (this._discard_pile_top.x) this._discard_piles.push(this._discard_pile_top);
this._discard_pile_top = card;
if (this._wild) {
this._wild = false;
this._discard_pile_top.color_n = this._wild_color_n;
}
this.treatCard();
}
async treatCard() {
switch (this._top_discard_pile.num) {
switch (this._discard_pile_top.num) {
case 10: // skip card
this._skip = true;
console.log('skip');
@ -243,23 +244,16 @@ export default class Room extends BasicCanvas {
console.log('next player draw 2 cards');
break;
case 13: // wild card (change color)
await ( this.changeColor() );
console.log('change color: ' + this._top_discard_pile.color_n);
console.log('change color: ' + this._discard_pile_top.color_n);
break;
case 14: // wild draw 4 card (change color)
this._draw4 = true;
await ( this.changeColor() );
console.log('next player draw 4 cards');
console.log('change color: ' + this._top_discard_pile.color_n);
console.log('change color: ' + this._discard_pile_top.color_n);
break;
default:
break;
}
}
changeColor() {
if (this._current_player.type === 'bot') this._top_discard_pile.color_n = this._current_player.changeColor();
}
}

15
src/js/rule.js Normal file
View File

@ -0,0 +1,15 @@
export default class Rule {
constructor() {
}
checkCardsMatch(card1, card2) {
if ( (card2.num <= 12 && card1.num === card2.num) // Normal card
|| (card2.num >= 13) // Change color card
|| (card2.color_n === card1.color_n) ) { // Color match
return true;
} else {
return false;
}
}
}

372
src/js/view.js Normal file
View File

@ -0,0 +1,372 @@
// view.js
/* Class */
import BasicCanvas from './basic_canvas.js';
import Room from './room.js';
import Card from './card.js';
import Rule from './rule.js';
export default class View {
constructor() {
this._data = {};
this._rule = new Rule();
this._is_initialized = false;
}
showMainMenu() {
// Create div
const div = document.createElement('div');
div.classList.add('uno-game-div');
div.style.top = global.uno_game_h/4 + 'px';
global.uno_game_div.appendChild( div );
// Create UNO title
const title = document.createElement('h1');
title.innerHTML = 'UNO Game';
title.classList.add('uno-game-main-title');
div.appendChild( title );
// label for input name
const label_n = document.createElement('label');
label_n.innerHTML = 'Input My Name';
label_n.classList.add('uno-game-label');
div.appendChild( label_n );
// Input name
const input_n = document.createElement('input');
input_n.type = 'text';
input_n.value = (this._my_name) ? this._my_name : '';
input_n.className = 'uno-game-input';
div.appendChild( input_n );
// Create find room button
const fr_btn = document.createElement('button');
fr_btn.classList.add('uno-game-btn');
fr_btn.innerHTML = 'Find Room';
div.appendChild( fr_btn );
// Add event
//this._fr_btn.addEventListener("click", e => {
// this._my_name = input_n.value;
// this.clearMainMenu();
// this.findRoom();
// this._is_host = false;
//});
// Create create room button
const cr_btn = document.createElement('button');
cr_btn.classList.add('uno-game-btn');
cr_btn.innerHTML = 'Create Room';
div.appendChild( cr_btn );
// Add event
cr_btn.addEventListener("click", e => {
this._my_name = input_n.value;
div.remove();
this.showCreateRoomMenu();
this._is_host = true;
});
}
showCreateRoomMenu() {
// Create div
const div = document.createElement('div');
div.classList.add('uno-game-div');
div.style.top = global.uno_game_h/4 + 'px';
global.uno_game_div.appendChild( div );
// Menu title
const title = document.createElement('h2');
title.innerHTML = 'Create Room';
title.classList.add('uno-game-menu-title');
div.appendChild( title );
// label for input room name
const label_rn = document.createElement('label');
label_rn.innerHTML = 'Input Room Name';
label_rn.classList.add('uno-game-label');
div.appendChild( label_rn );
// Input room name
const input_rn = document.createElement('input');
input_rn.type = 'text';
input_rn.className = 'uno-game-input';
div.appendChild( input_rn );
// Confirm button
const cfm_btn = document.createElement('button');
cfm_btn.classList.add('uno-game-btn');
cfm_btn.innerHTML = 'Confirm';
div.appendChild( cfm_btn );
cfm_btn.addEventListener('click', e => {
this._room = new Room( input_rn.value );
this.send({ctrl: 'addHuman', name: this._my_name});
this._data = JSON.parse( this._room.respondData() );
this.showRoom();
div.remove();
});
// Back button
const back_btn = document.createElement('button');
back_btn.classList.add('uno-game-btn');
back_btn.innerHTML = 'Back';
div.appendChild( back_btn );
back_btn.addEventListener('click', e => {
div.remove();
this.showMainMenu();
});
}
async showRoom() {
// Create div
this._div = document.createElement('div');
this._div.classList.add('uno-game-div');
this._div.style.top = global.uno_game_h/5 + 'px';
global.uno_game_div.appendChild( this._div );
// Menu title
const title = document.createElement('h2');
title.innerHTML = this._data.room.name;
title.classList.add('uno-game-menu-title');
this._div.appendChild( title );
if (this._is_host) {
// Add bot button
const ab_btn = document.createElement('button');
ab_btn.classList.add('uno-game-btn');
ab_btn.innerHTML = 'Add Bot';
this._div.appendChild( ab_btn );
ab_btn.addEventListener('click', e => {
this.send({ctrl: 'addBot'});
});
// Start game button
const sg_btn = document.createElement('button');
sg_btn.classList.add('uno-game-btn');
sg_btn.innerHTML = 'Star Game';
this._div.appendChild( sg_btn );
sg_btn.addEventListener('click', e => {
this.send({ctrl: 'startGame'});
this._div.remove();
});
}
// List players
const ul_pl = document.createElement('ul');
ul_pl.classList.add('uno-game-ul');
this._div.appendChild( ul_pl );
this._data.players.forEach( (player) => {
const li = document.createElement('li');
li.classList.add('uno-game-li');
li.innerHTML = player.name;
ul_pl.appendChild( li );
});
if (!this._loop_started) {
this._loop_started = true;
this.loopReceiveData();
}
}
async loopReceiveData() {
let is_finished = false;
while(true) {
const data = await ( JSON.parse( this._room.respondData() ) );
switch (data.game_status) {
case 'ready':
if ( JSON.stringify(this._data.players) != JSON.stringify(data.players) ) {
this._data = data;
this._div.remove();
this.showRoom();
}
break;
case 'start':
if (!this._is_initialized) {
await ( this.initCards() );
await new Promise(resolve => setTimeout(resolve, 500)); // Sleep .5 s
this._is_initialized = true;
}
if ( JSON.stringify(this._data.current_player) != JSON.stringify(data.current_player) ) {
console.log('current_player changed')
this._data = data;
await ( this.updateCards() );
if (data.current_player.name === this._my_name) this.myTurn();
}
break;
case 'finish':
this._data = data;
await ( this.updateCards() );
is_finished = true;
this.gameFinish();
break;
}
if (is_finished) break;
await new Promise(resolve => setTimeout(resolve, 500)); // Sleep .5 s
}
}
send(data) {
this._room.receive(data);
}
async initCards() {
console.log('init cards')
this._cards = [];
let count = 0
for (let x=0; x<14; x++) {
this._cards[x] = [];
}
for (let x=0; x<14; x++) {
let num = x;
for (let y=0; y<8; y++) {
const color_n = y%4;
if ( (x === 0) && (y >= 4) ) { // Skip blank card
continue;
}
if ( (x === 13) && (y >= 4) ) { // +4 cards
num = 14;
}
this._cards[x][y] = new Card(global.uno_game_w*6/16+count, global.uno_game_h/2, num, color_n);
count++;
}
}
}
updateCards() {
console.log('update cards view')
// Player Cards
const p_l = this._data.players.length;
let p_cnt = 0;
this._data.players.forEach( player => {
const c_l = player.cards.length;
for (let c=0; c<c_l; c++) {
const x = player.cards[c].x;
const y = player.cards[c].y;
if (player.name === this._my_name) {
const x0 = global.uno_game_w/4;
const dx = global.uno_game_w/2 / (c_l+1);
const y0 = global.uno_game_h*4/5;
this._cards[x][y].drawImageFront(x0 + dx*(c+1), y0);
} else {
const x0 = global.uno_game_w*p_cnt/(p_l-1);
const dx = global.uno_game_w/(p_l-1) / (c_l+2);
const y0 = this.ellipticalFormula(x0 + dx*(c+1), global.uno_game_w/2, global.uno_game_h);
this._cards[x][y].drawImageBack(x0 + dx*(c+1), y0);
}
}
if (!(player.name === this._my_name)) p_cnt++;
});
// Remain cards
for (let i=0; i<this._data.remain_cards.length; i++) {
const x = this._data.remain_cards[i].x;
const y = this._data.remain_cards[i].y;
this._cards[x][y].drawImageBack(global.uno_game_w*6/16+i, global.uno_game_h/2);
}
// Discard piles
for (let i=0; i<this._data.discard_piles.length; i++) {
const x = this._data.discard_piles[i].x;
const y = this._data.discard_piles[i].y;
this._cards[x][y].drawImageFront(global.uno_game_w*8/16+i, global.uno_game_h/2);
}
// Discard pile top
const discard_pile_top = this._cards[this._data.discard_pile_top.x][this._data.discard_pile_top.y];
discard_pile_top.drawImageFront(global.uno_game_w*8/16+this._data.discard_piles.length+1, global.uno_game_h/2);
}
ellipticalFormula(x, a, b) {
return b * ( 1 - Math.sqrt( 1 - (x/a - 1)**2 ) );
}
myTurn() {
console.log('my turn');
const top_draw_card_data = this._data.remain_cards[ this._data.remain_cards.length-1 ];
this._top_draw_card = this._cards[top_draw_card_data.x][top_draw_card_data.y];
this._top_draw_card.mouseEffect();
// Select card event
const bc_colors = [];
this._data.current_player.cards.forEach( card_data => {
const card = this._cards[card_data.x][card_data.y];
if (this._rule.checkCardsMatch(this._data.discard_pile_top, card.data)) {
card.mouseEffect();
card.canvas.addEventListener('click', () => {
// Show color change blocks
if (card.num >= 13) {
for (let i=0; i<4; i++) {
const w = global.uno_game_w;
const bc = new BasicCanvas(w/2+w*i/16, global.uno_game_h*3/4, w/16, w/16);
bc.fillColor(i);
bc.canvas.addEventListener('click', () => {
console.log('played card num: ' + card.num + ', color: ' + card.color_n);
// Remove event listeners
this._top_draw_card.resetEventListener();
this._data.current_player.cards.forEach( (card_data) => {
const card = this._cards[card_data.x][card_data.y];
card.resetEventListener();
});
bc_colors.forEach( bc_color => bc_color.remove() );
this.send({ctrl: 'changeColor', color_n: i});
this.send({
ctrl: 'playCard',
card: {num: card.num, color_n: card.color_n, x: card_data.x, y: card_data.y}
});
});
bc_colors.push(bc);
}
} else {
console.log('played card num: ' + card.num + ', color_n: ' + card.color_n);
// Remove event listener
this._top_draw_card.resetEventListener();
this._data.current_player.cards.forEach( (card_data) => {
const card = this._cards[card_data.x][card_data.y];
card.resetEventListener();
});
bc_colors.forEach( bc_color => bc_color.remove() );
this.send({
ctrl: 'playCard',
card: {num: card.num, color_n: card.color_n, x: card_data.x, y: card_data.y}
});
}
});
}
});
// Draw card event
this._top_draw_card.canvas.addEventListener('click', () => {
console.log('drawed card');
// Remove event listener
this._top_draw_card.resetEventListener();
this._data.current_player.cards.forEach( (card_data) => {
const card = this._cards[card_data.x][card_data.y];
card.resetEventListener();
});
bc_colors.forEach( bc_color => bc_color.remove() );
this.send({ctrl: 'drawCard'});
});
}
gameFinish() {
}
}

View File

@ -1,7 +1,9 @@
.uno-game-div {
.uno-game-main-div {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
/* Common */
@ -10,15 +12,16 @@
display: block;
}
/* Main view */
.uno-game-title {
position: absolute;
/* View */
// Title
.uno-game-main-title {
left: 0;
right: 0;
font-family: 'Waiting for the Sunrise', cursive;
font-weight: bold;
text-align: center;
font-size: calc(16vh);
overflow: hidden; /* Ensures the content is not revealed until the animation */
border-right: .15em solid orange; /* The typwriter cursor */
@ -39,6 +42,25 @@
from, to { border-color: transparent }
50% { border-color: orange; }
}
.uno-game-menu-title {
left: 0;
right: 0;
font-family: 'Waiting for the Sunrise', cursive;
text-align: center;
font-size: calc(14vh);
overflow: hidden; /* Ensures the content is not revealed until the animation */
white-space: nowrap; /* Keeps the content on a single line */
margin: 0 auto; /* Gives that scrolling effect as the typing happens */
}
// Div
.uno-game-div {
position: absolute;
left: 0;
right: 0;
}
// Button
.uno-game-btn-div {
position: absolute;
left: 0;
@ -53,8 +75,36 @@
color: white;
cursor: pointer; /* Pointer/hand icon */
display: block; /* Make the buttons appear below each other */
font-size: calc(6vh);
}
.uno-game-btn:hover {
background-color: olive;
}
// Label
.uno-game-label {
padding: 15px 32px;
margin: auto;
display: block;
text-align: center;
font-size: calc(6vh);
}
// Input
.uno-game-input {
padding: 15px 32px;
margin: auto;
display: block;
font-size: calc(6vh);
}
// UL
.uno-game-ul {
padding: 15px 32px;
margin: auto;
//display: block;
//font-size: calc(6vh);
}
.uno-game-li {
padding: 15px 32px;
margin: auto;
display: block;
font-size: calc(4vh);
}

File diff suppressed because one or more lines are too long