added visual effects and refactored the code
This commit is contained in:
parent
86596a14e7
commit
e74eaf0f80
18 changed files with 1663 additions and 170 deletions
365
4
Normal file
365
4
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
import { Deck } from "./deck";
|
||||
import { Hand } from "./hand";
|
||||
import { Tile } from "./tile";
|
||||
import { Group } from "./group";
|
||||
import { drawButtons, clickAction } from "./button";
|
||||
|
||||
export type mousePos = { x: number, y: number};
|
||||
|
||||
export class Game {
|
||||
private deck: Deck;
|
||||
private hands: Array<Hand>;
|
||||
private discards: Array<Array<Tile>>;
|
||||
private lastDiscard: number|undefined;
|
||||
private groups: Array<Array<Group>>;
|
||||
|
||||
// game values
|
||||
private turn = 0;
|
||||
private selectedTile: number|undefined = undefined;
|
||||
private canCall: boolean = false;
|
||||
private hasPicked: boolean = false;
|
||||
private hasPlayed: boolean = false;
|
||||
|
||||
// display parameter
|
||||
private BG_RECT = {color: "#007730", x: 0, y: 0, w: 1050, h: 1050};
|
||||
private sizeHand = 0.7;
|
||||
private sizeHiddenHand = 0.6;
|
||||
private sizeDiscard = 0.6;
|
||||
|
||||
// canvas
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
private cv: HTMLCanvasElement;
|
||||
private staticCtx: CanvasRenderingContext2D;
|
||||
private staticCv: HTMLCanvasElement;
|
||||
|
||||
public constructor(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
cv: HTMLCanvasElement,
|
||||
staticCtx: CanvasRenderingContext2D,
|
||||
staticCv: HTMLCanvasElement,
|
||||
red: boolean = false
|
||||
) {
|
||||
this.ctx = ctx;
|
||||
this.cv = cv;
|
||||
this.staticCtx = staticCtx;
|
||||
this.staticCv = staticCv;
|
||||
this.deck = new Deck(red);
|
||||
this.hands = [
|
||||
this.deck.getRandomHand(),
|
||||
this.deck.getRandomHand(),
|
||||
this.deck.getRandomHand(),
|
||||
this.deck.getRandomHand()
|
||||
];
|
||||
this.discards = [[], [], [], []];
|
||||
this.lastDiscard = undefined;
|
||||
this.groups = [[], [], [], []];
|
||||
this.pick(0);
|
||||
}
|
||||
|
||||
public draw(mp: mousePos) {
|
||||
// background
|
||||
this.staticCtx.clearRect(0, 0, this.cv.width, this.cv.height);
|
||||
this.staticCtx.fillStyle = this.BG_RECT.color;
|
||||
this.staticCtx.fillRect(
|
||||
this.BG_RECT.x,
|
||||
this.BG_RECT.y,
|
||||
this.BG_RECT.w,
|
||||
this.BG_RECT.h
|
||||
);
|
||||
|
||||
this.getSelected(mp);
|
||||
|
||||
this.drawGame();
|
||||
this.ctx.clearRect(0, 0, this.cv.width, this.cv.height);
|
||||
this.ctx.drawImage(this.staticCv, 0, 0);
|
||||
}
|
||||
|
||||
public getDeck(): Deck {
|
||||
return this.deck;
|
||||
}
|
||||
|
||||
public getHands(): Array<Hand> {
|
||||
return this.hands;
|
||||
}
|
||||
|
||||
public click(
|
||||
mp: mousePos,
|
||||
): void {
|
||||
let action = clickAction(
|
||||
mp.x,
|
||||
mp.y,
|
||||
this.canDoAChii().length > 0,
|
||||
this.canDoAPon(),
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
this.getSelected(mp);
|
||||
if (this.turn === 0 && this.selectedTile !== undefined) {
|
||||
this.discard(0, this.selectedTile as NonNullable<number>);
|
||||
this.turn++;
|
||||
}
|
||||
if (this.canDoAPon()) {
|
||||
this.pon(this.turn > 0 ? this.turn-1 : 3);
|
||||
}
|
||||
}
|
||||
|
||||
private getSelected(
|
||||
mp: mousePos,
|
||||
): void {
|
||||
const rect = this.cv.getBoundingClientRect();
|
||||
let x = 2.5 * 75 * 0.75;
|
||||
let y = 1050 - 250 * 0.6;
|
||||
|
||||
const mouseX = mp.x - rect.left - x;
|
||||
const mouseY = mp.y - rect.top;
|
||||
const s = 83.9;
|
||||
|
||||
let q = Math.floor(mouseX / (s * this.sizeHand));
|
||||
let r = mouseX - q * s * this.sizeHand;
|
||||
if (
|
||||
r <= (s - 3) * this.sizeHand &&
|
||||
q >= 0 &&
|
||||
q < this.hands[0].length() &&
|
||||
mouseY >= y &&
|
||||
mouseY <= y + 100 * this.sizeHand
|
||||
) {
|
||||
this.selectedTile = q;
|
||||
} else {
|
||||
this.selectedTile = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
private play(): void {
|
||||
if (
|
||||
this.turn !== 0
|
||||
) {
|
||||
if (!this.hasPicked) {
|
||||
this.pick(this.turn);
|
||||
this.hasPicked = true;
|
||||
} else if (!this.hasPlayed) {
|
||||
this.discard(this.turn, 0);
|
||||
this.hasPlayed = true;
|
||||
this.canCall = this.canDoAChii().length > 0 || this.canDoAPon();
|
||||
} else if (!this.canCall) {
|
||||
if (this.turn === 3) {
|
||||
this.turn = 0;
|
||||
this.pick(0);
|
||||
} else {
|
||||
this.turn++;
|
||||
}
|
||||
this.hasPicked = false;
|
||||
this.hasPlayed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private pick(player: number): void {
|
||||
this.hands[player].push(this.deck.pop());
|
||||
this.hands[player].isolate = true;
|
||||
}
|
||||
|
||||
private discard(player: number, n: number): void {
|
||||
let tile = this.hands[player].eject(n);
|
||||
tile.setTilt();
|
||||
this.discards[player].push(tile);
|
||||
this.hands[player].isolate = false;
|
||||
this.hands[player].sort();
|
||||
this.lastDiscard = player;
|
||||
}
|
||||
|
||||
private canDoAPon(): boolean {
|
||||
if (this.lastDiscard !== undefined && this.lastDiscard !== 0) {
|
||||
let t = this.discards[this.lastDiscard][this.discards[this.lastDiscard].length-1];
|
||||
return this.hands[0].count(t.getFamily(), t.getValue()) >= 2;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private pon(p: number): void {
|
||||
console.log(p, "\n");
|
||||
let t = this.discards[p].pop() as NonNullable<Tile>;
|
||||
this.lastDiscard = undefined;
|
||||
let t2 = this.hands[0].find(t.getFamily(), t.getValue()) as NonNullable<Tile>;
|
||||
let t3 = this.hands[0].find(t.getFamily(), t.getValue()) as NonNullable<Tile>;
|
||||
[t, t2, t3].forEach(t => t.setTilt());
|
||||
this.groups[0].push(new Group([t, t2, t3], p));
|
||||
}
|
||||
|
||||
private canDoAChii(): Array<number> {
|
||||
let chii = [] as Array<number>;
|
||||
if (
|
||||
this.lastDiscard !== undefined &&
|
||||
this.lastDiscard === 3 &&
|
||||
this.discards[this.lastDiscard][this.discards[this.lastDiscard].length-1].getFamily() < 4
|
||||
) {
|
||||
let t = this.discards[this.lastDiscard][this.discards[this.lastDiscard].length-1];
|
||||
let h = this.hands[0];
|
||||
if (
|
||||
h.count(t.getFamily(), t.getValue()-2) > 0 &&
|
||||
h.count(t.getFamily(), t.getValue()-1) > 0
|
||||
) {
|
||||
chii.push(t.getValue()-2);
|
||||
} else if (
|
||||
h.count(t.getFamily(), t.getValue()-1) > 0 &&
|
||||
h.count(t.getFamily(), t.getValue()+1) > 0
|
||||
) {
|
||||
chii.push(t.getValue()-1);
|
||||
} else if (
|
||||
h.count(t.getFamily(), t.getValue()+1) > 0 &&
|
||||
h.count(t.getFamily(), t.getValue()+2) > 0
|
||||
) {
|
||||
chii.push(t.getValue());
|
||||
}
|
||||
}
|
||||
return chii;
|
||||
}
|
||||
|
||||
private chii(minValue: number): void {
|
||||
}
|
||||
|
||||
private drawGame(): void {
|
||||
// update game
|
||||
this.play();
|
||||
|
||||
// hands
|
||||
this.drawHands();
|
||||
|
||||
// groups
|
||||
this.drawGroups(0.6);
|
||||
|
||||
// discards
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this.drawDiscard(
|
||||
i,
|
||||
this.hands[0].get(this.selectedTile) as NonNullable<Tile>
|
||||
);
|
||||
}
|
||||
|
||||
// called
|
||||
drawButtons(
|
||||
this.staticCtx,
|
||||
this.canDoAChii().length > 0,
|
||||
this.canDoAPon(),
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
private drawHands() {
|
||||
const pi = 3.141592;
|
||||
|
||||
this.hands[0].drawHand(
|
||||
this.staticCtx,
|
||||
2.5 * 75 * 0.75,
|
||||
1000 - 150 * this.sizeHand,
|
||||
5 * this.sizeHand,
|
||||
0.75,
|
||||
this.selectedTile,
|
||||
false,
|
||||
0
|
||||
);
|
||||
this.hands[1].drawHand(
|
||||
this.staticCtx,
|
||||
1000 - 150 * this.sizeHiddenHand,
|
||||
1000 - 75 * 5 * this.sizeHiddenHand,
|
||||
5 * this.sizeHiddenHand,
|
||||
this.sizeHiddenHand,
|
||||
undefined,
|
||||
true,
|
||||
- pi / 2
|
||||
);
|
||||
this.hands[2].drawHand(
|
||||
this.staticCtx,
|
||||
1000 - 75 * 5 * this.sizeHiddenHand,
|
||||
150 * this.sizeHiddenHand,
|
||||
5 * this.sizeHiddenHand,
|
||||
this.sizeHiddenHand,
|
||||
undefined,
|
||||
true,
|
||||
- pi
|
||||
);
|
||||
this.hands[3].drawHand(
|
||||
this.staticCtx,
|
||||
150 * this.sizeHiddenHand,
|
||||
75 * 5 * this.sizeHiddenHand,
|
||||
5 * this.sizeHiddenHand,
|
||||
this.sizeHiddenHand,
|
||||
undefined,
|
||||
true,
|
||||
pi / 2
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private drawGroups(
|
||||
size: number
|
||||
): void {
|
||||
let os = 25;
|
||||
const pi = 3.141592;
|
||||
for ( let p = 0; p < 4; p++) {
|
||||
let rotation = [0, -pi / 2, -pi, pi / 2][p];
|
||||
if (this.groups[p].length > 0) {
|
||||
for (let i = this.groups[p].length-1; i >= 0; i--) {
|
||||
this.groups[p][i].drawGroup(
|
||||
this.staticCtx,
|
||||
1050 - 240 - (260 + os) * size * i,
|
||||
1050 - 62,
|
||||
5,
|
||||
0.6,
|
||||
rotation
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private drawDiscard(
|
||||
p: number,
|
||||
highlitedTile: Tile|undefined
|
||||
): void {
|
||||
const pi = 3.141592;
|
||||
|
||||
this.staticCtx.save();
|
||||
this.staticCtx.translate(525, 525);
|
||||
this.staticCtx.rotate([0, -pi/2, -pi, pi/2][p])
|
||||
|
||||
let x = - this.sizeDiscard * 475 / 2;
|
||||
let y = this.sizeDiscard * (475 / 2 + 5);
|
||||
for (let i = 0; i < this.discards[p].length; i++) {
|
||||
let tile = this.discards[p][i];
|
||||
if (i < 12) {
|
||||
tile.drawTile(
|
||||
this.staticCtx,
|
||||
x + (i % 6) * 80 * this.sizeDiscard,
|
||||
y + Math.floor(i / 6) * 105 * this.sizeDiscard,
|
||||
this.sizeDiscard,
|
||||
false,
|
||||
0,
|
||||
highlitedTile?.isEqual(tile.getFamily(), tile.getValue())
|
||||
);
|
||||
} else {
|
||||
tile.drawTile(
|
||||
this.staticCtx,
|
||||
x + (i - 12) * 80 * this.sizeDiscard,
|
||||
y + 2 * 105 * this.sizeDiscard,
|
||||
this.sizeDiscard,
|
||||
false,
|
||||
0,
|
||||
highlitedTile?.isEqual(tile.getFamily(), tile.getValue())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.staticCtx.restore();
|
||||
}
|
||||
|
||||
public async preload(): Promise<void> {
|
||||
await this.deck.preload();
|
||||
await Promise.all(this.hands.map(h => h.preload()));
|
||||
}
|
||||
|
||||
}
|
||||
2
Makefile
2
Makefile
|
|
@ -2,7 +2,7 @@ DISPLAY = $(wildcard src/display/*.ts)
|
|||
OUTFILES = $(patsubst src/display/%.ts,build/%.js,$(DISPLAY))
|
||||
|
||||
all:
|
||||
npx webpack --mode production
|
||||
npx webpack --mode development
|
||||
|
||||
clean:
|
||||
rm -rf build/*
|
||||
|
|
|
|||
8
Makefile.save
Normal file
8
Makefile.save
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
DISPLAY = $(wildcard src/display/*.ts)
|
||||
OUTFILES = $(patsubst src/display/%.ts,build/%.js,$(DISPLAY))
|
||||
|
||||
all:
|
||||
npx webpack --mode production
|
||||
|
||||
clean:
|
||||
rm -rf build/*
|
||||
127
build/dp1.js
127
build/dp1.js
File diff suppressed because one or more lines are too long
127
build/dp2.js
127
build/dp2.js
File diff suppressed because one or more lines are too long
127
build/dp3.js
127
build/dp3.js
File diff suppressed because one or more lines are too long
147
build/dp4.js
147
build/dp4.js
File diff suppressed because one or more lines are too long
48
img/Regular/Gray.svg
Normal file
48
img/Regular/Gray.svg
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0"?>
|
||||
<svg width="300" height="400" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1">
|
||||
<defs id="defs4">
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath4243">
|
||||
<circle cx="-264.66" cy="-198.21" fill="#000000" fill-opacity="0.3" fill-rule="nonzero" id="circle4245" r="293.95" stroke="#000000" stroke-dashoffset="0" stroke-linejoin="round" stroke-miterlimit="4" stroke-width="19.13"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath7847">
|
||||
<ellipse cx="394" cy="552.36" fill="#822600" fill-rule="nonzero" id="ellipse7849" rx="349.5" ry="216" stroke-dashoffset="0" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke-width="12"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath4243-1">
|
||||
<circle cx="-264.66" cy="-198.21" fill="#000000" fill-opacity="0.3" fill-rule="nonzero" id="circle4245-4" r="293.95" stroke="#000000" stroke-dashoffset="0" stroke-linejoin="round" stroke-miterlimit="4" stroke-width="19.13"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath7876">
|
||||
<circle cx="-264.66" cy="-198.21" fill="#000000" fill-opacity="0.3" fill-rule="nonzero" id="circle7878" r="293.95" stroke="#000000" stroke-dashoffset="0" stroke-linejoin="round" stroke-miterlimit="4" stroke-width="19.13"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath14693">
|
||||
<rect fill="#a53c3c" fill-rule="nonzero" height="168.82" id="rect14695" rx="1.26" ry="3.75" stroke-dashoffset="0" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke-width="8" transform="matrix(0.99939083,-0.03489951,0.03489951,0.99939083,0,0)" width="131.78" x="-332.6" y="383.5"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath14952">
|
||||
<ellipse cx="-271.34" cy="647.26" fill="#a53c3c" fill-rule="nonzero" id="ellipse14954" rx="69.06" ry="116.91" stroke-dashoffset="0" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke-width="7" transform="matrix(0.99939083,-0.03489951,0.03489951,0.99939083,0,0)"/>
|
||||
</clipPath>
|
||||
<mask id="mask4222" maskUnits="userSpaceOnUse">
|
||||
<rect fill="#ff3737" fill-rule="nonzero" height="400.78" id="rect4224" ry="40" stroke-dashoffset="0" stroke-linejoin="round" stroke-miterlimit="4" stroke-width="10" width="300.06" x="0" y="652.28"/>
|
||||
</mask>
|
||||
<mask id="mask4216" maskUnits="userSpaceOnUse">
|
||||
<rect fill="#ff3737" fill-rule="nonzero" height="400.78" id="rect4218" ry="40" stroke-dashoffset="0" stroke-linejoin="round" stroke-miterlimit="4" stroke-width="10" transform="scale(-1,-1)" width="300.06" x="-451.94" y="-1325.6"/>
|
||||
</mask>
|
||||
<mask id="mask4222-0" maskUnits="userSpaceOnUse">
|
||||
<rect fill="#ff3737" fill-rule="nonzero" height="400.78" id="rect4224-8" ry="40" stroke-dashoffset="0" stroke-linejoin="round" stroke-miterlimit="4" stroke-width="10" width="300.06" x="0" y="652.28"/>
|
||||
</mask>
|
||||
<filter color-interpolation-filters="sRGB" id="filter4198-8">
|
||||
<feGaussianBlur id="feGaussianBlur4200-3" result="blur" stdDeviation="2.51 2.51"/>
|
||||
</filter>
|
||||
<mask id="mask4216-0" maskUnits="userSpaceOnUse">
|
||||
<rect fill="#ff3737" fill-rule="nonzero" height="400.78" id="rect4218-5" ry="40" stroke-dashoffset="0" stroke-linejoin="round" stroke-miterlimit="4" stroke-width="10" transform="scale(-1,-1)" width="300.06" x="-451.94" y="-1325.6"/>
|
||||
</mask>
|
||||
<pattern height="6" id="EMFhbasepattern" patternUnits="userSpaceOnUse" width="6" x="0" y="0"/>
|
||||
</defs>
|
||||
<metadata id="metadata7">image/svg+xml</metadata>
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<g id="layer1" opacity="0.35">
|
||||
<rect fill="#000000" fill-rule="nonzero" height="400.78" id="rect4164" ry="40" stroke-dashoffset="0" stroke-linejoin="round" stroke-miterlimit="4" stroke-width="10" width="300.06" y="-0.08"/>
|
||||
<path d="m-4.77,122.71c-4.88,-33.08 -12.08,-100.83 3.49,-123.01c20.28,-26.12 234.78,-20.43 264.59,1.85c12.9,8.74 -192.96,9.21 -216.21,37.83c-22.71,27.37 -46.45,126.45 -51.87,83.33z" fill="#000000" fill-rule="evenodd" filter="url(#filter4198-8)" id="path4166" mask="url(#mask4222-0)" stroke-width="1px" transform="translate(-1.34328e-07, -1.33688e-06)"/>
|
||||
<path d="m151.74,1677.38c-3.33,-9.32 -10.25,-68.46 5.31,-90.63c20.28,-26.12 219.44,-16.46 231.56,-9.93c11.07,5.32 -178.6,0.06 -204.85,34.87c-21.6,30 -26.5,82.17 -32.02,65.69z" fill="#000000" fill-opacity="0.2" fill-rule="evenodd" filter="url(#filter4198-8)" id="path4221" mask="url(#mask4216-0)" stroke-width="1px" transform="matrix(-1, 0, 0, -1, 451.938, 1977.89)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
12
index.html
12
index.html
|
|
@ -62,7 +62,7 @@
|
|||
<body>
|
||||
<nav class="menu">
|
||||
<ul class="menu-item">
|
||||
<a href="#" onclick="loadScript('accueil.js')">Accueil</a>
|
||||
<a href="#" onclick="loadScript('')">Introduction</a>
|
||||
</ul>
|
||||
<ul class="menu-item">
|
||||
<a href="#">Riichi Mahjong</a>
|
||||
|
|
@ -81,6 +81,14 @@
|
|||
<a href="#" onclick="loadScript('dp12.js')">Chap 12: Score: le calcul des points</a>
|
||||
<a href="#" onclick="loadScript('dp13.js')">Chap 13: Partie avec les scores</a>
|
||||
</div>
|
||||
</ul>
|
||||
<ul class="menu-item">
|
||||
<a href="#">Partie réelle</a>
|
||||
<div class="dropdown">
|
||||
<a href="#" onclick="loadScript('')">Chap 1: Placement des joueurs</a>
|
||||
<a href="#" onclick="loadScript('')">Chap 2: Construction des murs</a>
|
||||
<a href="#" onclick="loadScript('')">Chap 3: Désigner la pioche</a>
|
||||
<a href="#" onclick="loadScript('')">Chap 4: Le mur mort</a>
|
||||
</ul>
|
||||
<ul class="menu-item">
|
||||
<a href="#" onclick="loadScript('')">Riichi à trois</a>
|
||||
|
|
@ -123,7 +131,7 @@
|
|||
}
|
||||
|
||||
//script initial
|
||||
loadScript('dp1.js');
|
||||
loadScript('dp4.js');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
177
src/button.ts
Normal file
177
src/button.ts
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
type button_t = (
|
||||
arg0: CanvasRenderingContext2D,
|
||||
arg1: number,
|
||||
arg2: number
|
||||
) => void;
|
||||
|
||||
export function clickAction(
|
||||
x: number,
|
||||
y: number,
|
||||
chii: boolean,
|
||||
pon: boolean,
|
||||
kan: boolean,
|
||||
ron: boolean,
|
||||
tsumo: boolean
|
||||
): number {
|
||||
let buttons = [
|
||||
chii,
|
||||
pon,
|
||||
kan,
|
||||
ron,
|
||||
tsumo
|
||||
]
|
||||
if (buttons.some(c => c)) {
|
||||
buttons.unshift(true);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
let dx = 0;
|
||||
let size = 0.6;
|
||||
let xmin = 1025 - buttons.filter(c => c).length * 3 * size;
|
||||
console.log(x > xmin, "\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function drawButtons(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
chii: boolean,
|
||||
pon: boolean,
|
||||
kan: boolean,
|
||||
ron: boolean,
|
||||
tsumo: boolean
|
||||
): void {
|
||||
let buttons = [
|
||||
[chii, buttonChii],
|
||||
[pon, buttonPon],
|
||||
[kan, buttonKan],
|
||||
[ron, buttonRon],
|
||||
[tsumo, buttonTsumo]
|
||||
]
|
||||
if (buttons.some(c => c[0])) {
|
||||
buttons.unshift([true, buttonPass]);
|
||||
}
|
||||
let dx = 0;
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
if (buttons[i][0]) {
|
||||
(buttons[i][1] as button_t)(
|
||||
ctx,
|
||||
850 - dx * 120,
|
||||
835
|
||||
);
|
||||
dx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function button(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
r: number,
|
||||
w: number,
|
||||
h: number,
|
||||
color: string
|
||||
): void {
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
|
||||
ctx.moveTo(x + r, y);
|
||||
ctx.lineTo(x + w - r, y);
|
||||
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
|
||||
ctx.lineTo(x + w, y + h - r);
|
||||
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
||||
ctx.lineTo(x + r, y + h);
|
||||
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
|
||||
ctx.lineTo(x, y + r);
|
||||
ctx.quadraticCurveTo(x, y, x + r, y);
|
||||
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = "#606060";
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function buttonPass(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number
|
||||
): void {
|
||||
const r = 8;
|
||||
const w = 110;
|
||||
const h = 50;
|
||||
// button(ctx, x, y, r, w, h, "#FFAC4D");
|
||||
button(ctx, x, y, r, w, h, "#FF9030");
|
||||
ctx.fillStyle = "black";
|
||||
ctx.font = "30px garamond";
|
||||
ctx.fillText("Ignorer",x + w * 0.1, y + h/2 * 1.3);
|
||||
}
|
||||
|
||||
function buttonPon(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number
|
||||
): void {
|
||||
const r = 8;
|
||||
const w = 110;
|
||||
const h = 50;
|
||||
button(ctx, x, y, r, w, h, "#FFCC33");
|
||||
ctx.fillStyle = "black";
|
||||
ctx.font = "30px garamond";
|
||||
ctx.fillText("Pon",x + w * 0.28, y + h/2 * 1.3);
|
||||
}
|
||||
|
||||
function buttonChii(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number
|
||||
): void {
|
||||
const r = 8;
|
||||
const w = 110;
|
||||
const h = 50;
|
||||
button(ctx, x, y, r, w, h, "#FFCC33");
|
||||
ctx.fillStyle = "black";
|
||||
ctx.font = "30px garamond";
|
||||
ctx.fillText("Chii",x + w * 0.25, y + h/2 * 1.3);
|
||||
}
|
||||
|
||||
function buttonKan(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number
|
||||
): void {
|
||||
const r = 8;
|
||||
const w = 110;
|
||||
const h = 50;
|
||||
button(ctx, x, y, r, w, h, "#FFCC33");
|
||||
ctx.fillStyle = "black";
|
||||
ctx.font = "30px garamond";
|
||||
ctx.fillText("Kan",x + w * 0.28, y + h/2 * 1.3);
|
||||
}
|
||||
|
||||
function buttonRon(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number
|
||||
): void {
|
||||
const r = 8;
|
||||
const w = 110;
|
||||
const h = 50;
|
||||
button(ctx, x, y, r, w, h, "#FF3060");
|
||||
ctx.fillStyle = "black";
|
||||
ctx.font = "30px garamond";
|
||||
ctx.fillText("Ron",x + w * 0.28, y + h/2 * 1.3);
|
||||
}
|
||||
|
||||
function buttonTsumo(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number
|
||||
): void {
|
||||
const r = 8;
|
||||
const w = 110;
|
||||
const h = 50;
|
||||
button(ctx, x, y, r, w, h, "#FF3060");
|
||||
ctx.fillStyle = "black";
|
||||
ctx.font = "30px garamond";
|
||||
ctx.fillText("Tsumo",x + w * 0.13, y + h/2 * 1.3);
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ export class Deck {
|
|||
if (i < 4) { // famille
|
||||
for (let j = 1; j < 10; j++) {
|
||||
const tile = this.find(i, j) as NonNullable<Tile>;
|
||||
tile.drawTile(ctx, posX, posY, size);
|
||||
tile.drawTile(ctx, posX, posY, size, false, 0, false);
|
||||
posX += (75 + xOffset) * size;
|
||||
}
|
||||
posX = x;
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ async function display () {
|
|||
}
|
||||
|
||||
// dynamic hand
|
||||
ehand.isolate = true;
|
||||
ehand.drawHand(offScreenCtx, x, 800, 5, size, selectedTile);
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
|
@ -113,8 +114,8 @@ async function display () {
|
|||
if (selectedTile !== undefined) {
|
||||
edeck.push(ehand.eject(selectedTile));
|
||||
edeck.shuffle();
|
||||
ehand.push(edeck.pop());
|
||||
ehand.sort();
|
||||
ehand.push(edeck.pop());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ function drawFrame() {
|
|||
HANDS[6].drawHand(staticCtx, x + (75+xos)*size, y + 2*(100+yos)*size, 5, 0.75);
|
||||
HANDS[7].drawHand(staticCtx, x + 2*(75+xos)*size, y + 2*(100+yos)*size, 5, 0.75);
|
||||
|
||||
HANDS[8].isolate = true;
|
||||
HANDS[8].drawHand(staticCtx, 100, 800, 5, size, selectedTile);
|
||||
|
||||
let groups = HANDS[8].toGroup();
|
||||
|
|
@ -117,8 +118,8 @@ function initEventListeners() {
|
|||
if (selectedTile !== undefined) {
|
||||
DECKS[0].push(HANDS[8].eject(selectedTile));
|
||||
DECKS[0].shuffle();
|
||||
HANDS[8].push(DECKS[0].pop());
|
||||
HANDS[8].sort();
|
||||
HANDS[8].push(DECKS[0].pop());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { Deck } from "../deck";
|
||||
import { Hand } from "../hand";
|
||||
import { Group } from "../group";
|
||||
import { Game } from "../game"
|
||||
|
||||
// Configuration globale
|
||||
const CANVAS_ID = "myCanvas";
|
||||
const BG_COLOR = "#007730";
|
||||
const BG_RECT = { x: 50, y: 50, w: 1000, h: 1000 };
|
||||
const BG_RECT = { x: 0, y: 0, w: 1050, h: 1050 };
|
||||
var MOUSE = { x: 0, y: 0 };
|
||||
const FPS = 30;
|
||||
const FRAME_INTERVAL = 1000 / FPS;
|
||||
|
||||
|
|
@ -15,13 +14,6 @@ const DECKS: Array<Deck> = [];
|
|||
const HANDS: Array<Hand> = [];
|
||||
var GAME: Game|undefined;
|
||||
|
||||
// variables for the game
|
||||
var selectedTile: number|undefined;
|
||||
var turn: number = 0;
|
||||
var hasPlayed: boolean = false;
|
||||
var pCurrentTime = 0;
|
||||
var pDeltaTime = 500;
|
||||
|
||||
// Optimisation des références
|
||||
let animationFrameId: number;
|
||||
let lastFrameTime = 0;
|
||||
|
|
@ -30,6 +22,8 @@ const callbacks: Array<() => void> = [];
|
|||
// Pré-calcul des dimensions
|
||||
const canvas = document.getElementById(CANVAS_ID) as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d") as NonNullable<CanvasRenderingContext2D>;
|
||||
canvas.width = BG_RECT.w;
|
||||
canvas.height = BG_RECT.h;
|
||||
|
||||
// Cache statique
|
||||
const staticCanvas = document.createElement('canvas') as HTMLCanvasElement;
|
||||
|
|
@ -37,51 +31,14 @@ const staticCtx = staticCanvas.getContext("2d") as NonNullable<CanvasRenderingCo
|
|||
staticCanvas.width = canvas.width;
|
||||
staticCanvas.height = canvas.height;
|
||||
|
||||
// Pré-rendu du fond
|
||||
function prerenderBackground() {
|
||||
staticCtx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
staticCtx.fillStyle = BG_COLOR;
|
||||
staticCtx.fillRect(BG_RECT.x, BG_RECT.y, BG_RECT.w, BG_RECT.h);
|
||||
};
|
||||
|
||||
function drawFrame() {
|
||||
if (!ctx) return;
|
||||
|
||||
let size = 0.65;
|
||||
let os = 5;
|
||||
let w = size * (450 + 5 * os);
|
||||
let center = 525;
|
||||
|
||||
// Effacement intelligent (uniquement la zone nécessaire)
|
||||
prerenderBackground();
|
||||
|
||||
// Ici viendrait le dessin des éléments dynamiques
|
||||
// Par exemple:
|
||||
// drawDeck();
|
||||
// drawHands();
|
||||
// staticCtx.fillStyle = "#005530";
|
||||
// staticCtx.fillRect(center - w/2, center - w/2, w, w);
|
||||
|
||||
GAME?.drawGame(staticCtx, size, 0.6, selectedTile);
|
||||
|
||||
// Dessin du cache statique
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(staticCanvas, 0, 0);
|
||||
GAME?.draw(MOUSE);
|
||||
}
|
||||
|
||||
function animationLoop(currentTime: number) {
|
||||
animationFrameId = requestAnimationFrame(animationLoop);
|
||||
|
||||
// bot playing
|
||||
if (turn !== 0 && (currentTime - pCurrentTime) > pDeltaTime && (GAME as NonNullable<Game>).getDeck().length() > 0) {
|
||||
pCurrentTime = currentTime;
|
||||
let n = Math.floor(Math.random() * (GAME as NonNullable<Game>).getHands()[turn].length());
|
||||
GAME?.discard(turn, n);
|
||||
turn = (turn + 1) % 4;
|
||||
GAME?.pick(turn);
|
||||
GAME?.getHands()[0].sort();
|
||||
}
|
||||
|
||||
const deltaTime = currentTime - lastFrameTime;
|
||||
if (deltaTime < FRAME_INTERVAL) return;
|
||||
|
||||
|
|
@ -91,36 +48,12 @@ function animationLoop(currentTime: number) {
|
|||
|
||||
function initEventListeners() {
|
||||
const handlers = {
|
||||
mousemove: (e: MouseEvent) => {
|
||||
// Logique de gestion du mouvement de la souris
|
||||
let size = 0.75;
|
||||
let x = 2.5 * 75 * 0.75;
|
||||
let y = 1000 - 150 * 0.6;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const mouseX = e.clientX - rect.left - x;
|
||||
const mouseY = e.clientY - rect.top;
|
||||
|
||||
let q = Math.floor(mouseX / (80 * size));
|
||||
let r = mouseX - q * 80 * size;
|
||||
if (
|
||||
r <= 75 &&
|
||||
q >= 0 &&
|
||||
q < (GAME as NonNullable<Game>).getHands()[0].length() &&
|
||||
mouseY >= y &&
|
||||
mouseY <= y + 100 * size
|
||||
) {
|
||||
selectedTile = q;
|
||||
} else {
|
||||
selectedTile = undefined;
|
||||
}
|
||||
},
|
||||
mousedown: (e: MouseEvent) => {
|
||||
// Logique de gestion du clic de souris
|
||||
if (turn === 0 && selectedTile !== undefined && (GAME as NonNullable<Game>).getDeck().length() > 0) {
|
||||
GAME?.discard(0, selectedTile);
|
||||
turn++;
|
||||
GAME?.pick(turn);
|
||||
}
|
||||
GAME?.click(e);
|
||||
},
|
||||
mousemove: (e: MouseEvent) => {
|
||||
MOUSE.x = e.x;
|
||||
MOUSE.y = e.y;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -151,6 +84,7 @@ export async function initDisplay() {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log("Load begining\n");
|
||||
// Préchargement des ressources si nécessaire
|
||||
// const deck = new Deck();
|
||||
// await preloadDeck(deck);
|
||||
|
|
@ -158,14 +92,17 @@ export async function initDisplay() {
|
|||
);
|
||||
HANDS.push(
|
||||
);
|
||||
GAME = new Game();
|
||||
GAME = new Game(
|
||||
ctx,
|
||||
canvas,
|
||||
staticCtx,
|
||||
staticCanvas
|
||||
);
|
||||
await Promise.all(DECKS.map(d => preloadDeck(d)));
|
||||
await Promise.all(HANDS.map(h => preloadHand(h)));
|
||||
await GAME?.preload();
|
||||
|
||||
GAME.pick(0);
|
||||
GAME.getHands()[0].sort();
|
||||
|
||||
console.log("Loaded completed\n");
|
||||
initEventListeners();
|
||||
requestAnimationFrame(animationLoop);
|
||||
window.cleanup = cleanup;
|
||||
|
|
|
|||
412
src/game.ts
412
src/game.ts
|
|
@ -1,13 +1,49 @@
|
|||
import { Deck } from "./deck";
|
||||
import { Hand } from "./hand";
|
||||
import { Tile } from "./tile";
|
||||
import { Group } from "./group";
|
||||
import { drawButtons, clickAction } from "./button";
|
||||
|
||||
export type mousePos = { x: number, y: number};
|
||||
|
||||
export class Game {
|
||||
private deck: Deck;
|
||||
private hands: Array<Hand>;
|
||||
private discards: Array<Array<Tile>>;
|
||||
private lastDiscard: number|undefined;
|
||||
private groups: Array<Array<Group>>;
|
||||
|
||||
public constructor(red: boolean = false) {
|
||||
// game values
|
||||
private turn = 0;
|
||||
private selectedTile: number|undefined = undefined;
|
||||
private canCall: boolean = false;
|
||||
private hasPicked: boolean = false;
|
||||
private hasPlayed: boolean = false;
|
||||
private lastPlayed: number = Date.now();
|
||||
|
||||
// display parameter
|
||||
private BG_RECT = {color: "#007730", x: 0, y: 0, w: 1050, h: 1050};
|
||||
private sizeHand = 0.7;
|
||||
private sizeHiddenHand = 0.6;
|
||||
private sizeDiscard = 0.6;
|
||||
|
||||
// canvas
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
private cv: HTMLCanvasElement;
|
||||
private staticCtx: CanvasRenderingContext2D;
|
||||
private staticCv: HTMLCanvasElement;
|
||||
|
||||
public constructor(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
cv: HTMLCanvasElement,
|
||||
staticCtx: CanvasRenderingContext2D,
|
||||
staticCv: HTMLCanvasElement,
|
||||
red: boolean = false
|
||||
) {
|
||||
this.ctx = ctx;
|
||||
this.cv = cv;
|
||||
this.staticCtx = staticCtx;
|
||||
this.staticCv = staticCv;
|
||||
this.deck = new Deck(red);
|
||||
this.hands = [
|
||||
this.deck.getRandomHand(),
|
||||
|
|
@ -16,6 +52,29 @@ export class Game {
|
|||
this.deck.getRandomHand()
|
||||
];
|
||||
this.discards = [[], [], [], []];
|
||||
this.lastDiscard = undefined;
|
||||
this.groups = [[], [], [], []];
|
||||
|
||||
this.hands[0].sort();
|
||||
this.pick(0);
|
||||
}
|
||||
|
||||
public draw(mp: mousePos) {
|
||||
// background
|
||||
this.staticCtx.clearRect(0, 0, this.cv.width, this.cv.height);
|
||||
this.staticCtx.fillStyle = this.BG_RECT.color;
|
||||
this.staticCtx.fillRect(
|
||||
this.BG_RECT.x,
|
||||
this.BG_RECT.y,
|
||||
this.BG_RECT.w,
|
||||
this.BG_RECT.h
|
||||
);
|
||||
|
||||
this.getSelected(mp);
|
||||
|
||||
this.drawGame();
|
||||
this.ctx.clearRect(0, 0, this.cv.width, this.cv.height);
|
||||
this.ctx.drawImage(this.staticCv, 0, 0);
|
||||
}
|
||||
|
||||
public getDeck(): Deck {
|
||||
|
|
@ -26,67 +85,321 @@ export class Game {
|
|||
return this.hands;
|
||||
}
|
||||
|
||||
public drawGame(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
discardSize: number,
|
||||
handSize: number,
|
||||
selectedTile: number|undefined = undefined
|
||||
public click(
|
||||
mp: mousePos,
|
||||
): void {
|
||||
let action = clickAction(
|
||||
mp.x,
|
||||
mp.y,
|
||||
this.canDoAChii().length > 0,
|
||||
this.canDoAPon(),
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
if (action === -1 && this.canCall) {
|
||||
if (this.turn === 3) {
|
||||
this.canCall = false;
|
||||
this.turn = 0;
|
||||
this.pick(0);
|
||||
} else {
|
||||
this.turn++;
|
||||
}
|
||||
this.hasPicked = false;
|
||||
this.hasPlayed = false;
|
||||
} else {
|
||||
this.getSelected(mp);
|
||||
if (this.turn === 0 && this.selectedTile !== undefined) {
|
||||
this.discard(0, this.selectedTile as NonNullable<number>);
|
||||
this.turn++;
|
||||
}
|
||||
if (this.canDoAPon()) {
|
||||
this.pon(this.turn > 0 ? this.turn-1 : 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getSelected(
|
||||
mp: mousePos,
|
||||
): void {
|
||||
const rect = this.cv.getBoundingClientRect();
|
||||
let x = 2.5 * 75 * 0.75;
|
||||
let y = 1050 - 250 * 0.6;
|
||||
|
||||
const mouseX = mp.x - rect.left - x;
|
||||
const mouseY = mp.y - rect.top;
|
||||
const s = 83.9;
|
||||
|
||||
let q = Math.floor(mouseX / (s * this.sizeHand));
|
||||
let r = mouseX - q * s * this.sizeHand;
|
||||
if (
|
||||
r <= (s - 3) * this.sizeHand &&
|
||||
q >= 0 &&
|
||||
q < this.hands[0].length() &&
|
||||
mouseY >= y &&
|
||||
mouseY <= y + 100 * this.sizeHand
|
||||
) {
|
||||
this.selectedTile = q;
|
||||
} else {
|
||||
this.selectedTile = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
private play(): void {
|
||||
if (
|
||||
this.turn !== 0
|
||||
) { // bot playing
|
||||
if (!this.hasPicked) { // begin of his turn
|
||||
this.pick(this.turn);
|
||||
this.hasPicked = true;
|
||||
} else if (!this.hasPlayed) { // middle of his turn
|
||||
if (Date.now() - this.lastPlayed > 700) {
|
||||
this.lastPlayed = Date.now();
|
||||
let n = Math.floor(this.hands[this.turn].length() * Math.random());
|
||||
this.discard(this.turn, n);
|
||||
this.hasPlayed = true;
|
||||
this.canCall = this.canDoAPon();
|
||||
}
|
||||
} else if (!this.canCall) { // end of his turn
|
||||
if (this.turn === 3) {
|
||||
this.turn = 0;
|
||||
this.pick(0);
|
||||
} else {
|
||||
this.turn++;
|
||||
}
|
||||
this.hasPicked = false;
|
||||
this.hasPlayed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private pick(player: number): void {
|
||||
this.hands[player].push(this.deck.pop());
|
||||
this.hands[player].isolate = true;
|
||||
}
|
||||
|
||||
private discard(player: number, n: number): void {
|
||||
let tile = this.hands[player].eject(n);
|
||||
this.hands[player].sort();
|
||||
tile.setTilt();
|
||||
this.discards[player].push(tile);
|
||||
this.hands[player].isolate = false;
|
||||
this.hands[player].sort();
|
||||
this.lastDiscard = player;
|
||||
this.lastPlayed = Date.now();
|
||||
}
|
||||
|
||||
private canDoAPon(): boolean {
|
||||
if (this.lastDiscard !== undefined && this.lastDiscard !== 0) {
|
||||
let t = this.discards[this.lastDiscard][this.discards[this.lastDiscard].length-1];
|
||||
return this.hands[0].count(t.getFamily(), t.getValue()) >= 2;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private pon(p: number): void {
|
||||
console.log("Pon !\n");
|
||||
let t = this.discards[p].pop() as NonNullable<Tile>;
|
||||
this.lastDiscard = undefined;
|
||||
let t2 = this.hands[0].find(t.getFamily(), t.getValue()) as NonNullable<Tile>;
|
||||
let t3 = this.hands[0].find(t.getFamily(), t.getValue()) as NonNullable<Tile>;
|
||||
[t, t2, t3].forEach(t => t.setTilt());
|
||||
this.groups[0].push(new Group([t, t2, t3], p));
|
||||
}
|
||||
|
||||
private canDoAChii(): Array<number> {
|
||||
let chii = [] as Array<number>;
|
||||
if (
|
||||
this.lastDiscard !== undefined &&
|
||||
this.lastDiscard === 3 &&
|
||||
this.discards[this.lastDiscard][this.discards[this.lastDiscard].length-1].getFamily() < 4
|
||||
) {
|
||||
let t = this.discards[this.lastDiscard][this.discards[this.lastDiscard].length-1];
|
||||
let h = this.hands[0];
|
||||
if (
|
||||
h.count(t.getFamily(), t.getValue()-2) > 0 &&
|
||||
h.count(t.getFamily(), t.getValue()-1) > 0
|
||||
) {
|
||||
chii.push(t.getValue()-2);
|
||||
} else if (
|
||||
h.count(t.getFamily(), t.getValue()-1) > 0 &&
|
||||
h.count(t.getFamily(), t.getValue()+1) > 0
|
||||
) {
|
||||
chii.push(t.getValue()-1);
|
||||
} else if (
|
||||
h.count(t.getFamily(), t.getValue()+1) > 0 &&
|
||||
h.count(t.getFamily(), t.getValue()+2) > 0
|
||||
) {
|
||||
chii.push(t.getValue());
|
||||
}
|
||||
}
|
||||
return chii;
|
||||
}
|
||||
|
||||
private chii(minValue: number): void {
|
||||
console.log("Chii !\n");
|
||||
}
|
||||
|
||||
private drawGame(): void {
|
||||
// update game
|
||||
this.play();
|
||||
|
||||
// hands
|
||||
this.drawHands();
|
||||
|
||||
// groups
|
||||
this.drawGroups(0.6);
|
||||
|
||||
// discards
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this.drawDiscard(
|
||||
i,
|
||||
this.hands[0].get(this.selectedTile) as NonNullable<Tile>
|
||||
);
|
||||
}
|
||||
|
||||
// called
|
||||
drawButtons(
|
||||
this.staticCtx,
|
||||
this.canDoAChii().length > 0,
|
||||
this.canDoAPon(),
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
private drawHands() {
|
||||
const pi = 3.141592;
|
||||
|
||||
this.hands[0].drawHand(
|
||||
ctx,
|
||||
this.staticCtx,
|
||||
2.5 * 75 * 0.75,
|
||||
1000 - 150 * handSize,
|
||||
5 * handSize,
|
||||
1000 - 150 * this.sizeHand,
|
||||
5 * this.sizeHand,
|
||||
0.75,
|
||||
selectedTile,
|
||||
this.selectedTile,
|
||||
false,
|
||||
0
|
||||
);
|
||||
this.hands[1].drawHand(
|
||||
ctx,
|
||||
1000 - 150 * handSize,
|
||||
1000 - 75 * 5 * handSize,
|
||||
5 * handSize,
|
||||
handSize,
|
||||
this.staticCtx,
|
||||
1000 - 150 * this.sizeHiddenHand,
|
||||
1000 - 75 * 5 * this.sizeHiddenHand,
|
||||
5 * this.sizeHiddenHand,
|
||||
this.sizeHiddenHand,
|
||||
undefined,
|
||||
true,
|
||||
- pi / 2
|
||||
);
|
||||
this.hands[2].drawHand(
|
||||
ctx,
|
||||
1000 - 75 * 5 * handSize,
|
||||
150 * handSize,
|
||||
5 * handSize,
|
||||
handSize,
|
||||
this.staticCtx,
|
||||
1000 - 75 * 5 * this.sizeHiddenHand,
|
||||
150 * this.sizeHiddenHand,
|
||||
5 * this.sizeHiddenHand,
|
||||
this.sizeHiddenHand,
|
||||
undefined,
|
||||
true,
|
||||
- pi
|
||||
);
|
||||
this.hands[3].drawHand(
|
||||
ctx,
|
||||
150 * handSize,
|
||||
75 * 5 * handSize,
|
||||
5 * handSize,
|
||||
handSize,
|
||||
this.staticCtx,
|
||||
150 * this.sizeHiddenHand,
|
||||
75 * 5 * this.sizeHiddenHand,
|
||||
5 * this.sizeHiddenHand,
|
||||
this.sizeHiddenHand,
|
||||
undefined,
|
||||
true,
|
||||
pi / 2
|
||||
);
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this.drawDiscrad(ctx, i, discardSize);
|
||||
}
|
||||
|
||||
private drawGroups(
|
||||
size: number
|
||||
): void {
|
||||
let os = 25;
|
||||
const pi = 3.141592;
|
||||
for ( let p = 0; p < 4; p++) {
|
||||
let rotation = [0, -pi / 2, -pi, pi / 2][p];
|
||||
if (this.groups[p].length > 0) {
|
||||
for (let i = this.groups[p].length-1; i >= 0; i--) {
|
||||
this.groups[p][i].drawGroup(
|
||||
this.staticCtx,
|
||||
1050 - 240 - (260 + os) * size * i,
|
||||
1050 - 62,
|
||||
5,
|
||||
0.6,
|
||||
rotation
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public pick(player: number): void {
|
||||
this.hands[player].push(this.deck.pop());
|
||||
private drawDiscard(
|
||||
p: number,
|
||||
highlitedTile: Tile|undefined
|
||||
): void {
|
||||
const pi = 3.141592;
|
||||
|
||||
this.staticCtx.save();
|
||||
this.staticCtx.translate(525, 525);
|
||||
this.staticCtx.rotate([0, -pi/2, -pi, pi/2][p])
|
||||
|
||||
let x = - this.sizeDiscard * 475 / 2;
|
||||
let y = this.sizeDiscard * (475 / 2 + 5);
|
||||
for (let i = 0; i < this.discards[p].length; i++) {
|
||||
let tile = this.discards[p][i];
|
||||
if (i < 12) {
|
||||
tile.drawTile(
|
||||
this.staticCtx,
|
||||
x + (i % 6) * 80 * this.sizeDiscard,
|
||||
y + Math.floor(i / 6) * 105 * this.sizeDiscard,
|
||||
this.sizeDiscard,
|
||||
false,
|
||||
0,
|
||||
highlitedTile?.isEqual(tile.getFamily(), tile.getValue())
|
||||
);
|
||||
} else {
|
||||
tile.drawTile(
|
||||
this.staticCtx,
|
||||
x + (i - 12) * 80 * this.sizeDiscard,
|
||||
y + 2 * 105 * this.sizeDiscard,
|
||||
this.sizeDiscard,
|
||||
false,
|
||||
0,
|
||||
highlitedTile?.isEqual(tile.getFamily(), tile.getValue())
|
||||
);
|
||||
}
|
||||
}
|
||||
if (this.lastDiscard === p) {
|
||||
const j = this.discards[p].length - 1;
|
||||
let tx =
|
||||
j < 12 ?
|
||||
x + (j % 6) * 80 * this.sizeDiscard :
|
||||
x + (j - 12) * 80 * this.sizeDiscard;
|
||||
let ty =
|
||||
j < 12 ?
|
||||
y + Math.floor(j / 6) * 105 * this.sizeDiscard :
|
||||
y + 2 * 105 * this.sizeDiscard;
|
||||
tx += 75 / 2 * this.sizeDiscard;
|
||||
ty += 115 * this.sizeDiscard;
|
||||
const ts = 10;
|
||||
this.staticCtx.fillStyle = "#ff0000";
|
||||
this.staticCtx.beginPath();
|
||||
this.staticCtx.moveTo(tx, ty);
|
||||
|
||||
this.staticCtx.lineTo(tx + ts/2, ty + 0.866 * ts);
|
||||
this.staticCtx.lineTo(tx - ts/2, ty + 0.866 * ts);
|
||||
this.staticCtx.lineTo(tx, ty);
|
||||
|
||||
this.staticCtx.fill();
|
||||
this.staticCtx.stroke();
|
||||
}
|
||||
|
||||
public discard(player: number, n: number): void {
|
||||
let tile = this.hands[player].eject(n);
|
||||
tile.setTilt();
|
||||
this.discards[player].push(tile);
|
||||
this.staticCtx.restore();
|
||||
}
|
||||
|
||||
public async preload(): Promise<void> {
|
||||
|
|
@ -94,37 +407,4 @@ export class Game {
|
|||
await Promise.all(this.hands.map(h => h.preload()));
|
||||
}
|
||||
|
||||
private drawDiscrad(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
p: number,
|
||||
discardSize: number
|
||||
): void {
|
||||
const pi = 3.141592;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(525, 525);
|
||||
ctx.rotate([0, -pi/2, -pi, pi/2][p])
|
||||
|
||||
let x = - discardSize * 475 / 2;
|
||||
let y = discardSize * (475 / 2 + 5);
|
||||
for (let i = 0; i < this.discards[p].length; i++) {
|
||||
if (i < 12) {
|
||||
this.discards[p][i].drawTile(
|
||||
ctx,
|
||||
x + (i % 6) * 80 * discardSize,
|
||||
y + Math.floor(i / 6) * 105 * discardSize,
|
||||
discardSize
|
||||
);
|
||||
} else {
|
||||
this.discards[p][i].drawTile(
|
||||
ctx,
|
||||
x + (i - 12) * 80 * discardSize,
|
||||
y + 2 * 105 * discardSize,
|
||||
discardSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
115
src/group.ts
115
src/group.ts
|
|
@ -2,9 +2,11 @@ import { Tile } from "./tile"
|
|||
|
||||
export class Group {
|
||||
private tiles: Array<Tile>;
|
||||
private stolenFrom: number|undefined;
|
||||
|
||||
public constructor(tiles: Array<Tile> = []) {
|
||||
public constructor(tiles: Array<Tile> = [], stolenFrom: number|undefined = undefined) {
|
||||
this.tiles = tiles;
|
||||
this.stolenFrom = stolenFrom;
|
||||
}
|
||||
|
||||
public push(tile: Tile): void {
|
||||
|
|
@ -18,4 +20,115 @@ export class Group {
|
|||
public getTiles(): Array<Tile> {
|
||||
return this.tiles;
|
||||
}
|
||||
|
||||
public drawGroup(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
os: number,
|
||||
size: number,
|
||||
rotation: number,
|
||||
): void {
|
||||
let v = 75 * size;
|
||||
let w = 90 * size;
|
||||
let vx = Math.cos(rotation);
|
||||
let vy = Math.sin(rotation);
|
||||
let osx = Math.sin(rotation) * 25 * size / 2;
|
||||
let osy = Math.cos(rotation) * 25 * size / 2;
|
||||
if (!this.stolenFrom) {
|
||||
//TODO error
|
||||
}
|
||||
let p = 3 - (this.stolenFrom as NonNullable<number>);
|
||||
|
||||
if (p === 0) {
|
||||
this.tiles[0].drawTile(
|
||||
ctx,
|
||||
x + osx,
|
||||
y + osy,
|
||||
size,
|
||||
false,
|
||||
3.141592 / 2,
|
||||
false
|
||||
);
|
||||
this.tiles[1].drawTile(
|
||||
ctx,
|
||||
x + vx * w,
|
||||
y + vy * w,
|
||||
size,
|
||||
false,
|
||||
0,
|
||||
false
|
||||
);
|
||||
this.tiles[2].drawTile(
|
||||
ctx,
|
||||
x + vx * (w + v + os * size),
|
||||
y + vy * (w + v + os * size),
|
||||
size,
|
||||
false,
|
||||
0,
|
||||
false
|
||||
);
|
||||
|
||||
} else if (p === 1) {
|
||||
this.tiles[0].drawTile(
|
||||
ctx,
|
||||
x,
|
||||
y,
|
||||
size,
|
||||
false,
|
||||
0,
|
||||
false
|
||||
);
|
||||
this.tiles[1].drawTile(
|
||||
ctx,
|
||||
x + vx * w + osx,
|
||||
y + vy * w + osy,
|
||||
size,
|
||||
false,
|
||||
0 - 3.141592 / 2,
|
||||
false
|
||||
);
|
||||
this.tiles[2].drawTile(
|
||||
ctx,
|
||||
x + vx * (w + v + 3 *os * size),
|
||||
y + vy * (w + v + 3 *os * size),
|
||||
size,
|
||||
false,
|
||||
0,
|
||||
false
|
||||
);
|
||||
|
||||
} else if (p === 2) {
|
||||
this.tiles[0].drawTile(
|
||||
ctx,
|
||||
x,
|
||||
y,
|
||||
size,
|
||||
false,
|
||||
0,
|
||||
false
|
||||
);
|
||||
this.tiles[1].drawTile(
|
||||
ctx,
|
||||
x + vx * (v + os * size),
|
||||
y + vy * (v + os * size),
|
||||
size,
|
||||
false,
|
||||
0,
|
||||
false
|
||||
);
|
||||
this.tiles[2].drawTile(
|
||||
ctx,
|
||||
x + vx * (w + v + os * size) + osx,
|
||||
y + vy * (w + v + os * size) + osy,
|
||||
size,
|
||||
false,
|
||||
0 - 3.141592 / 2,
|
||||
false
|
||||
);
|
||||
|
||||
} else {
|
||||
//TODO error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
src/hand.ts
26
src/hand.ts
|
|
@ -3,6 +3,7 @@ import { Group } from "./group"
|
|||
|
||||
export class Hand {
|
||||
private tiles: Array<Tile>;
|
||||
public isolate: boolean = false;
|
||||
|
||||
public constructor(stiles: string = "") {
|
||||
this.tiles = [];
|
||||
|
|
@ -26,7 +27,7 @@ export class Hand {
|
|||
return this.tiles.length;
|
||||
}
|
||||
|
||||
public push(tile: Tile): undefined {
|
||||
public push(tile: Tile): void {
|
||||
this.tiles.push(tile);
|
||||
}
|
||||
|
||||
|
|
@ -59,6 +60,14 @@ export class Hand {
|
|||
return tile as NonNullable<Tile>;
|
||||
}
|
||||
|
||||
public get(idTile: number|undefined): Tile|undefined {
|
||||
if (idTile !== undefined) {
|
||||
return this.tiles[idTile];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public sort(): undefined {
|
||||
this.tiles.sort((a, b) => a.isLessThan(b) ? -1 : 1);
|
||||
}
|
||||
|
|
@ -147,11 +156,18 @@ export class Hand {
|
|||
let vx = Math.cos(rotation) * v;
|
||||
let vy = Math.sin(rotation) * v;
|
||||
for (let i = 0; i < this.tiles.length; i++) {
|
||||
let e = (i === this.tiles.length - 1 && this.isolate) ? 10 : 0;
|
||||
if (i === focusedTiled) {
|
||||
this.tiles[i].drawTile(
|
||||
ctx,
|
||||
x + i * vx + 25 * size * Math.sin(rotation),
|
||||
y + i * vy - 25 * size * Math.cos(rotation),
|
||||
x +
|
||||
i * vx +
|
||||
25 * size * Math.sin(rotation) +
|
||||
e * size * Math.cos(rotation),
|
||||
y +
|
||||
i * vy -
|
||||
25 * size * Math.cos(rotation) +
|
||||
e * size * Math.sin(rotation),
|
||||
size,
|
||||
hidden,
|
||||
rotation
|
||||
|
|
@ -159,8 +175,8 @@ export class Hand {
|
|||
} else {
|
||||
this.tiles[i].drawTile(
|
||||
ctx,
|
||||
x + i * vx,
|
||||
y + i * vy,
|
||||
x + i * vx + e * size * Math.cos(rotation),
|
||||
y + i * vy + e * size * Math.sin(rotation),
|
||||
size,
|
||||
hidden,
|
||||
rotation
|
||||
|
|
|
|||
23
src/tile.ts
23
src/tile.ts
|
|
@ -5,6 +5,7 @@ export class Tile {
|
|||
private imgSrc: string;
|
||||
private imgFront: HTMLImageElement;
|
||||
private imgBack: HTMLImageElement;
|
||||
private imgGray: HTMLImageElement;
|
||||
private img: HTMLImageElement;
|
||||
private tilt: number;
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ export class Tile {
|
|||
this.imgSrc = "";
|
||||
this.imgFront = new Image();
|
||||
this.imgBack = new Image();
|
||||
this.imgGray = new Image();
|
||||
this.img = new Image();
|
||||
this.tilt = 0;
|
||||
this.setImgSrc();
|
||||
|
|
@ -28,12 +30,16 @@ export class Tile {
|
|||
return this.value;
|
||||
}
|
||||
|
||||
public isEqual(family: number, value: number): boolean {
|
||||
return this.family === family && this.value === value;
|
||||
}
|
||||
|
||||
public isRed(): boolean {
|
||||
return this.red;
|
||||
}
|
||||
|
||||
public setTilt(): void {
|
||||
this.tilt = (1 - 2 * Math.random()) * 0.05;
|
||||
this.tilt = (1 - 2 * Math.random()) * 0.04;
|
||||
}
|
||||
|
||||
public drawTile(
|
||||
|
|
@ -42,7 +48,8 @@ export class Tile {
|
|||
y: number,
|
||||
size: number,
|
||||
hidden: boolean = false,
|
||||
rotation: number = 0
|
||||
rotation: number = 0,
|
||||
gray: boolean = false
|
||||
): void {
|
||||
ctx.save();
|
||||
ctx.translate(x + (75 * size) / 2, y + (100 * size) / 2);
|
||||
|
|
@ -69,6 +76,15 @@ export class Tile {
|
|||
75 * size,
|
||||
100 * size
|
||||
);
|
||||
if (gray) {
|
||||
ctx.drawImage(
|
||||
this.imgGray,
|
||||
-(75 * size) / 2,
|
||||
-(100 * size) / 2,
|
||||
75 * size,
|
||||
100 * size
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
|
@ -89,6 +105,8 @@ export class Tile {
|
|||
this.imgFront.onerror = null;
|
||||
this.imgBack.onload = null;
|
||||
this.imgBack.onerror = null;
|
||||
this.imgGray.onload = null;
|
||||
this.imgGray.onerror = null;
|
||||
this.img.onload = null;
|
||||
this.img.onerror = null;
|
||||
}
|
||||
|
|
@ -113,6 +131,7 @@ export class Tile {
|
|||
await Promise.all([
|
||||
this.loadImg(this.imgFront, "/img/Regular/Front.svg"),
|
||||
this.loadImg(this.imgBack, "/img/Regular/Back.svg"),
|
||||
this.loadImg(this.imgGray, "/img/Regular/Gray.svg"),
|
||||
this.loadImg(this.img, this.imgSrc)
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue