Skip to content

Commit

Permalink
implement stacking
Browse files Browse the repository at this point in the history
  • Loading branch information
soryy708 committed May 21, 2023
1 parent 0137824 commit 093e064
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 23 deletions.
60 changes: 60 additions & 0 deletions src/front/engine/gfx/renderable/torus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
Mesh,
MeshLambertMaterial,
RingGeometry,
TorusGeometry,
Vector3,
} from 'three';
import { Renderable } from './interface';
import { RendererContext } from '../context';
import { Vector } from '../../math/vector';
import { VectorInternalAdapter } from '../../math/vector-internal-adapter';

type Dimensions = {
radius: number;
width: number;
};

export class Torus implements Renderable {
private mesh: Mesh;
private material = new MeshLambertMaterial({ color: 0xffffff });

constructor(dimensions: Dimensions) {
const geometry = new TorusGeometry(
dimensions.radius,
dimensions.width,
8,
24,
);
this.mesh = new Mesh(geometry, this.material);
}

registerRenderer(context: RendererContext): void {
context.scene.getInternal().add(this.mesh);
}

setColor(rgb: { r: number; g: number; b: number }): void {
this.material.setValues({
color: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,
});
}

setPosition(position: Vector<[number, number, number]>): void {
this.mesh.position.set(
position.get(0),
position.get(1),
position.get(2),
);
}

getInternalMesh(): Mesh {
return this.mesh;
}

getWorldCoordinates(): Vector<[number, number, number]> {
const vector3 = new Vector3();
return VectorInternalAdapter.toVector(
this.mesh.getWorldPosition(vector3),
);
}
}
12 changes: 12 additions & 0 deletions src/front/engine/io/pointer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Vector } from '../math/vector';

type OnMoveCallback = (offset: Vector<[number, number]>) => void;
type OnClickCallback = () => void;

export class Pointer {
private moveCallbacks: Array<OnMoveCallback> = [];
private clickCallbacks: Array<OnClickCallback> = [];
private ndcX = NaN;
private ndcY = NaN;

Expand All @@ -18,12 +20,22 @@ export class Pointer {
callback(new Vector(event.movementX, event.movementY));
});
});

element.addEventListener('click', () => {
this.clickCallbacks.forEach((callback) => {
callback();
});
});
}

onMove(callback: OnMoveCallback): void {
this.moveCallbacks.push(callback);
}

onClick(callback: OnClickCallback): void {
this.clickCallbacks.push(callback);
}

lock(): void {
if (document.pointerLockElement === this.element) {
return;
Expand Down
57 changes: 47 additions & 10 deletions src/front/game/board/abstract-board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,41 @@ export type BoardPosition = 0 | 1 | 2 | 3;
* Headless, so it can be used without rendering, e.g. for AI.
*/
export class AbstractBoard {
private state: BoardState;
/**
* Matrix [x][y][z]
* [0][0] = bottom left of board
* z = perpendicular to board
*/
private state: BoardState = [
[
[null, null, null, null],
[null, null, null, null],
[null, null, null, null],
[null, null, null, null],
],
[
[null, null, null, null],
[null, null, null, null],
[null, null, null, null],
[null, null, null, null],
],
[
[null, null, null, null],
[null, null, null, null],
[null, null, null, null],
[null, null, null, null],
],
[
[null, null, null, null],
[null, null, null, null],
[null, null, null, null],
[null, null, null, null],
],
];

canStack(x: BoardPosition, y: BoardPosition): boolean {
const topOfColumnZ = 3;
return this.getCell(x, y, topOfColumnZ) !== null; // Each column is a stack, so there can't be gaps
return this.getCell(x, y, topOfColumnZ) === null; // Each column is a stack, so there can't be gaps
}

stack(
Expand All @@ -26,35 +56,42 @@ export class AbstractBoard {
if (!this.canStack(position.x, position.y)) {
throw new Error("Can't stack fully stacked column");
}
const { content, height } = this.getHighest(position.x, position.y);
const z: BoardPosition = (height + (content ? 1 : 0)) as never;
const z: BoardPosition = this.getStackHeight(position);
this.setCell({ ...position, z }, piece);
}

getCell(x: BoardPosition, y: BoardPosition, z: BoardPosition): BoardCell {
return this.state[y][x][z];
return this.state[x][y][z];
}

public getStackHeight(position: {
x: BoardPosition;
y: BoardPosition;
}): BoardPosition {
const { height } = this.getHighest(position.x, position.y);
return height;
}

private setCell(
position: { x: BoardPosition; y: BoardPosition; z: BoardPosition },
content: BoardCell,
): void {
if (this.state[position.y][position.x][position.z] !== null) {
if (this.state[position.x][position.y][position.z] !== null) {
throw new Error('Tried to overwrite a non-empty cell');
}
this.state[position.y][position.x][position.z] = content;
this.state[position.x][position.y][position.z] = content;
}

private getHighest(
x: BoardPosition,
y: BoardPosition,
): { content: BoardCell; height: BoardPosition } {
const column = this.state[y][x];
const reversed = column.reverse();
const column = this.state[x][y];
const reversed = [...column].reverse();
const index = reversed.findIndex((c) => c !== null);
if (index === -1) {
return { content: null, height: 0 };
}
return { content: reversed[index], height: (4 - index - 1) as never };
return { content: reversed[index], height: (4 - index) as never };
}
}
56 changes: 46 additions & 10 deletions src/front/game/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { Vector3 } from 'three';
import { Engine } from '../engine';
import { Box } from '../engine/gfx/renderable/box';
import { OrbitIo } from '../engine/io/orbit';
import { Pointer } from '../engine/io/pointer';
import { Raycaster } from '../engine/io/raycaster';
import { Vector } from '../engine/math/vector';
import { VectorInternalAdapter } from '../engine/math/vector-internal-adapter';
import { BoardPosition } from './board/abstract-board';
import { Board } from './board/board';

const boardRotation: Vector<[number, number, number]> = new Vector(
-0.75,
-0.3,
-0.01,
);
import { Cross } from './piece/cross';
import { Nought } from './piece/nought';
import { GamePiece } from './piece/interface';

type Player = 'player1' | 'player2';

Expand All @@ -24,6 +19,7 @@ export class Game {
private orbitIo: OrbitIo;
private raycaster: Raycaster;
private pointer: Pointer;
private boardPosition: { x: BoardPosition; y: BoardPosition } = null;

constructor(private engine: Engine) {}

Expand All @@ -34,13 +30,13 @@ export class Game {
this.board.onHover((position) => this.onBoardHover(position));
this.board.onLeave(() => this.onBoardLeave());
this.board.bootstrap(this.engine);
this.board.rotate(boardRotation);
this.cursor.setColor({ r: 255, g: 255, b: 0 });
this.cursor.hide();
this.cursor.rotate(boardRotation);
this.cursor.registerRenderer(this.engine.getRendererContext());
this.orbitIo = new OrbitIo(this.engine);
this.orbitIo.setTarget(this.board.getCenter());

this.pointer.onClick(() => this.onClick());
}

tick(_deltaTime: number) {
Expand All @@ -52,12 +48,52 @@ export class Game {
x: BoardPosition;
y: BoardPosition;
}): void {
this.boardPosition = position;
this.cursor.show();
const coordinates = this.board.getCellWorldCoordinates(position);
this.cursor.setPosition(coordinates);
}

private onBoardLeave() {
this.boardPosition = null;
this.cursor.hide();
}

private onClick() {
if (!this.boardPosition) {
return;
}

if (this.board.canStack(this.boardPosition.x, this.boardPosition.y)) {
const piece = this.buildCurrentPlayerPiece({
...this.boardPosition,
z: this.board.getStackHeight(this.boardPosition),
});
this.board.stack(this.boardPosition, piece);
this.advanceTurn();
}
}

private buildCurrentPlayerPiece(boardPosition: {
x: BoardPosition;
y: BoardPosition;
z: BoardPosition;
}): GamePiece {
const pieceTypes = {
player1: Cross,
player2: Nought,
};
const PieceType = pieceTypes[this.currentPlayer];
const piece = new PieceType(boardPosition);
piece.bootstrap(this.engine);
return piece;
}

private advanceTurn(): void {
if (this.currentPlayer === 'player1') {
this.currentPlayer = 'player2';
} else {
this.currentPlayer = 'player1';
}
}
}
46 changes: 45 additions & 1 deletion src/front/game/piece/cross.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,47 @@
import { Engine } from '../../engine';
import { Box } from '../../engine/gfx/renderable/box';
import { Group } from '../../engine/gfx/renderable/group';
import { Vector } from '../../engine/math/vector';
import { BoardPosition } from '../board/abstract-board';
import { GamePiece } from './interface';

export class Cross implements GamePiece {}
const cellSize = 4;
const boardLength = cellSize * 4;
const crossDepth = 1;
const zDistance = 2;

export class Cross implements GamePiece {
private group = new Group();

constructor(
private boardPosition: {
x: BoardPosition;
y: BoardPosition;
z: BoardPosition;
},
) {
const color = { r: 255, g: 0, b: 0 };

const box1 = new Box({ width: 1, height: cellSize, depth: crossDepth });
box1.rotate(new Vector(0, 0, 1));
box1.setColor(color);
this.group.add(box1);

const box2 = new Box({ width: 1, height: cellSize, depth: crossDepth });
box2.rotate(new Vector(0, 0, -1));
box2.setColor(color);
this.group.add(box2);

this.group.translate(
new Vector(
cellSize * this.boardPosition.x - boardLength / 2,
cellSize * this.boardPosition.y - boardLength / 2,
zDistance * this.boardPosition.z - boardLength / 2 + crossDepth,
),
);
}

bootstrap(engine: Engine): void {
this.group.registerRenderer(engine.getRendererContext());
}
}
6 changes: 5 additions & 1 deletion src/front/game/piece/interface.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export interface GamePiece {}
import { Engine } from '../../engine';

export interface GamePiece {
bootstrap(engine: Engine): void;
}
40 changes: 39 additions & 1 deletion src/front/game/piece/nought.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,41 @@
import { Engine } from '../../engine';
import { Torus } from '../../engine/gfx/renderable/torus';
import { Vector } from '../../engine/math/vector';
import { BoardPosition } from '../board/abstract-board';
import { GamePiece } from './interface';

export class Nought implements GamePiece {}
const cellSize = 4;
const boardLength = cellSize * 4;
const torusWidth = 0.5;
const zDistance = 2;

export class Nought implements GamePiece {
private ring: Torus;

constructor(
private boardPosition: {
x: BoardPosition;
y: BoardPosition;
z: BoardPosition;
},
) {
this.ring = new Torus({
radius: cellSize / 2 - torusWidth,
width: torusWidth,
});
this.ring.setColor({ r: 0, g: 0, b: 255 });
this.ring.setPosition(
new Vector<[number, number, number]>(
cellSize * this.boardPosition.x - boardLength / 2,
cellSize * this.boardPosition.y - boardLength / 2,
zDistance * this.boardPosition.z -
boardLength / 2 +
torusWidth * 2,
),
);
}

bootstrap(engine: Engine): void {
this.ring.registerRenderer(engine.getRendererContext());
}
}

0 comments on commit 093e064

Please sign in to comment.