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:
parent
0d31d4e570
commit
e45bc45ff4
85
src/index.js
85
src/index.js
@ -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')
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
268
src/js/room.js
268
src/js/room.js
@ -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
15
src/js/rule.js
Normal 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
372
src/js/view.js
Normal 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() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
58
uno_game.js
58
uno_game.js
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user