Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Oを描画 #6

Merged
merged 3 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added images/v0.6.0.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mizu-ts",
"version": "0.5.0",
"version": "0.6.0",
"description": "Mizu-ts is a joke script that simulates water(H2o) generation in TypeScript.",
"type": "module",
"scripts": {
Expand Down
86 changes: 86 additions & 0 deletions src/atoms/O.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { Coordinate } from './Coordinate';

export class O {
public x = 0;
public y = 0;
public w = 0;
public h = 0;
public r = 0;
public color = '';

private name = 'O';
private vx = 0;
private vy = 0;

constructor(
private sw: number,
private sh: number,
) {}

public initializeDrawingProperties(coord: Coordinate): void {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('Failed to get canvas 2D context');
}
const fontSize = 24 * this.getScale();
ctx.font = `${fontSize}px sans-serif`;
const txtSize = ctx.measureText(this.getName()).width;

this.x = coord.x;
this.y = coord.y;
this.w = txtSize;
this.h = txtSize;
this.r = txtSize / 2;
this.color = this.getColor();
}

public getName(): string {
return this.name;
}

public getColor(): string {
return `#${Math.random().toString(16).slice(-6)}`;
}

public getScale(): number {
return this.sw < 768 ? 1.0 : 1.2;
}

public updatePosition(): void {
const randomAngle = 2 * Math.PI * Math.random();
const speedFactor = 0.075;

this.vx += speedFactor * Math.cos(randomAngle);
this.vy += speedFactor * Math.sin(randomAngle);

const maxSpeed = 1.05;
const currentSpeed = Math.sqrt(this.vx ** 2 + this.vy ** 2);
if (currentSpeed > maxSpeed) {
this.vx = (this.vx / currentSpeed) * maxSpeed;
this.vy = (this.vy / currentSpeed) * maxSpeed;
}

this.x += this.vx;
this.y += this.vy;

if (this.x > this.sw + this.w / 2) this.x = -(this.w / 2);
if (this.x + this.w < 0) this.x = this.sw + this.w / 2;
if (this.y > this.sh + this.h / 2) this.y = -(this.h / 2);
if (this.y + this.h < 0) this.y = this.sh + this.h / 2;
}

public render(ctx: CanvasRenderingContext2D): void {
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const fontSize = 24 * this.getScale();
ctx.font = `${fontSize}px sans-serif`;
ctx.fillStyle = this.color;
ctx.shadowColor = '#888';
ctx.shadowOffsetX = 1;
ctx.shadowOffsetY = 1;
ctx.shadowBlur = 1;

ctx.fillText(this.getName(), this.x, this.y);
}
}
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MizuSimulator } from './simulator/MizuSimulator';
document.addEventListener('DOMContentLoaded', () => {
const simulator = new MizuSimulator();
const scale = simulator.getScale();
simulator.init(30 * scale);
simulator.init(30 * scale, 20 * scale);

const loop = () => {
simulator.renderFrame();
Expand Down
30 changes: 26 additions & 4 deletions src/simulator/MizuSimulator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Coordinate } from '../atoms/Coordinate';
import { H } from '../atoms/H';
import { O } from '../atoms/O';

export class MizuSimulator {
private h: H[] = [];
private o: O[] = [];
private cw: number;
private ch: number;
private ctx: CanvasRenderingContext2D;
Expand Down Expand Up @@ -37,9 +39,12 @@ export class MizuSimulator {
this.bufferCtx = bufferCtx;
}

public init(hCount: number): void {
public init(hCount: number, oCount: number): void {
for (let i = 0; i < hCount; i++) {
this.h.push(this.createAtom());
this.h.push(this.createHAtom());
}
for (let i = 0; i < oCount; i++) {
this.o.push(this.createOAtom());
}
}

Expand All @@ -48,6 +53,7 @@ export class MizuSimulator {
this.bufferCtx.fillRect(0, 0, this.cw, this.ch);

this.renderH(this.h);
this.renderO(this.o);

this.ctx.drawImage(this.bufferCanvas, 0, 0);
}
Expand All @@ -62,14 +68,22 @@ export class MizuSimulator {
return 1.5;
}

private createAtom(): H {
private createHAtom(): H {
const x = this.cw * Math.random();
const y = this.ch * Math.random();
const h = new H(this.cw, this.ch);
h.initializeDrawingProperties(new Coordinate(x, y));
return h;
}

private createOAtom(): O {
const x = this.cw * Math.random();
const y = this.ch * Math.random();
const o = new O(this.cw, this.ch);
o.initializeDrawingProperties(new Coordinate(x, y));
return o;
}

private renderH(atoms: H[]): void {
for (let i = 0; i < atoms.length; i++) {
const _h = atoms[i];
Expand All @@ -90,10 +104,18 @@ export class MizuSimulator {
_h.mergeAndRender(this.bufferCtx, new Coordinate(_h.x, _h.y));

// 衝突した相手は新しい H に差し替え
atoms[j] = this.createAtom();
atoms[j] = this.createHAtom();

break;
}
}
}

private renderO(atoms: O[]): void {
for (let i = 0; i < atoms.length; i++) {
const _o = atoms[i];
_o.updatePosition();
_o.render(this.bufferCtx);
}
}
}
42 changes: 42 additions & 0 deletions tests/atoms/O.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, expect, it } from 'vitest';
import { Coordinate } from '../../src/atoms/Coordinate';
import { O } from '../../src/atoms/O';

describe('O クラスのテスト', () => {
const sw = 800;
const sh = 600;

it('プロパティが初期化されること', () => {
const o = new O(sw, sh);
o.initializeDrawingProperties(new Coordinate(200, 300));

expect(o.x).toBe(200);
expect(o.y).toBe(300);
expect(o.getName()).toBe('O');
});

it('位置がランダムに更新され、範囲内に収まること', () => {
const o = new O(sw, sh);
o.initializeDrawingProperties(new Coordinate(200, 300));

for (let i = 0; i < 100; i++) {
o.updatePosition();

expect(o.x).toBeGreaterThanOrEqual(0);
expect(o.x).toBeLessThanOrEqual(sw);
expect(o.y).toBeGreaterThanOrEqual(0);
expect(o.y).toBeLessThanOrEqual(sh);
}
});

it('描画処理がエラーなく実行されること', () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Canvas context not available');

const o = new O(sw, sh);
o.initializeDrawingProperties(new Coordinate(300, 400));

expect(() => o.render(ctx)).not.toThrow();
});
});
41 changes: 27 additions & 14 deletions tests/simulator/MizuSimulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,53 @@ describe('MizuSimulator クラスのテスト', () => {
simulator = new MizuSimulator();
});

it('初期化時に指定された数の H が生成されること', () => {
simulator.init(10);
it('初期化時に指定された数の H と O が生成されること', () => {
simulator.init(10, 5);
expect(simulator['h'].length).toBe(10);
expect(simulator['o'].length).toBe(5);
});

it('H がランダムな座標で初期化されること', () => {
simulator.init(1);
it('H と O がランダムな座標で初期化されること', () => {
simulator.init(1, 1);

const h = simulator['h'][0];
expect(h.x).toBeGreaterThanOrEqual(0);
expect(h.x).toBeLessThanOrEqual(simulator['cw']);
expect(h.y).toBeGreaterThanOrEqual(0);
expect(h.y).toBeLessThanOrEqual(simulator['ch']);

const o = simulator['o'][0];
expect(o.x).toBeGreaterThanOrEqual(0);
expect(o.x).toBeLessThanOrEqual(simulator['cw']);
expect(o.y).toBeGreaterThanOrEqual(0);
expect(o.y).toBeLessThanOrEqual(simulator['ch']);
});

it('フレームの描画がエラーなく実行されること', () => {
simulator.init(5);
simulator.init(5, 5);
expect(() => simulator.renderFrame()).not.toThrow();
});

it('フレーム描画時に H が正しく移動すること', () => {
simulator.init(1);
const initialX = simulator['h'][0].x;
const initialY = simulator['h'][0].y;
it('フレーム描画時に H と O が正しく移動すること', () => {
simulator.init(1, 1);

const initialHX = simulator['h'][0].x;
const initialHY = simulator['h'][0].y;
const initialOX = simulator['o'][0].x;
const initialOY = simulator['o'][0].y;

simulator.renderFrame();

expect(simulator['h'][0].x).not.toBe(initialX);
expect(simulator['h'][0].y).not.toBe(initialY);
expect(simulator['h'][0].x).not.toBe(initialHX);
expect(simulator['h'][0].y).not.toBe(initialHY);
expect(simulator['o'][0].x).not.toBe(initialOX);
expect(simulator['o'][0].y).not.toBe(initialOY);
});

it('H同士の衝突時に結合が正しく行われること', () => {
simulator.init(2);
it('H 同士が衝突時に正しく結合されること', () => {
simulator.init(2, 0);
simulator['h'][0].initializeDrawingProperties(new Coordinate(100, 100));
simulator['h'][1].initializeDrawingProperties(new Coordinate(105, 105)); // 衝突する位置
simulator['h'][1].initializeDrawingProperties(new Coordinate(105, 105));

simulator.renderFrame();

Expand Down
Loading