Skip to content

Instantly share code, notes, and snippets.

@screeny05
Last active February 4, 2018 15:16
Show Gist options
  • Save screeny05/0a001cac09176d27eaf1a3ce8c73dd91 to your computer and use it in GitHub Desktop.
Save screeny05/0a001cac09176d27eaf1a3ce8c73dd91 to your computer and use it in GitHub Desktop.
type Color = string;
class Player {
constructor(public name: string, public color: Color){ }
}
type Field = Player|null;
class GameField {
private fields: Field[][];
constructor(public width: number, public height: number){
this.clear();
}
clear(): void {
this.fields = new Array(this.width).fill(null).map(() => new Array(this.height).fill(null).map(() => null));
}
at(x: number, y: number): Field {
return this.fields[x][y];
}
tryAt(x: number, y: number): Field {
if(!this.isWithinBounds(x, y)){
return null;
}
return this.at(x, y);
}
setPlayerAt(x: number, y: number, player: Player): void {
this.fields[x][y] = player;
}
isWithinBounds(x: number, y: number): boolean {
return x >= 0 && y >= 0 && x < this.width && y < this.height;
}
iterate(cb: (field: Field, x?: number, y?: number) => boolean|void): void {
for(let x = 0; x < this.width; x++){
for(let y = 0; y < this.height; y++){
if(cb(this.at(x, y), x, y) === false){
return;
}
}
}
}
getCol(x: number): Field[] {
const seq: Field[] = [];
for(let y = 0; y < this.height; y++){
seq.push(this.at(x, y));
}
return seq;
}
getRow(y: number): Field[] {
const seq: Field[] = [];
for(let x = 0; x < this.width; x++){
seq.push(this.at(x, y));
}
return seq;
}
isFull(): boolean {
let isFull = true;
this.iterate(field => {
if(!field){
isFull = false;
}
return false;
});
return isFull;
}
getBottomMostEmptyField(x: number): number|null {
for(let y = this.height - 1; y >= 0; y--){
if(!this.at(x, y)){
return y;
}
}
return null;
}
getPossibleWinSequenceAt(x: number, y: number, player: Player, minMatches: number): Field[] | null {
let foundSeq: Field[] | null = null;
[
this.getHorizontalSequence.bind(this),
this.getVerticalSequence.bind(this),
this.getDiagonalAscSequence.bind(this),
this.getDiagonalDescSequence.bind(this)
].find(sequenceFactory => {
const seq = sequenceFactory(x, y, minMatches);
const valid = this.checkFieldSequence(seq, player, minMatches);
if(valid){
foundSeq = seq;
}
return valid;
});
return foundSeq;
}
checkFieldSequence(fields: Field[], player: Player, minMatches: number): boolean {
let matchCount = 0;
fields.forEach(field => {
if(field === player){
matchCount++;
} else {
matchCount = 0;
}
});
return matchCount >= minMatches;
}
sequenceMapper(count: number, cb: (i: number) => Field): Field[] {
const seq: Field[] = [];
for(let i = -count + 1; i < count; i++){
seq.push(cb(i));
}
return seq;
}
getHorizontalSequence(x: number, y: number, count: number): Field[] {
return this.sequenceMapper(count, i => this.tryAt(x + i, y));
}
getVerticalSequence(x: number, y: number, count: number): Field[] {
return this.sequenceMapper(count, i => this.tryAt(x, y + i));
}
getDiagonalAscSequence(x: number, y: number, count: number): Field[] {
return this.sequenceMapper(count, i => this.tryAt(x + i, y + i));
}
getDiagonalDescSequence(x: number, y: number, count: number): Field[] {
return this.sequenceMapper(count, i => this.tryAt(x + i, y - i));
}
}
enum GameStatus {
RUNNING,
WON,
FULL,
}
class Game {
player: Player[] = [];
playerTurnIndex: number = 0;
field: GameField;
status: GameStatus = GameStatus.RUNNING;
minMatches: number = 4;
constructor(){
this.player.push(new Player('Sebastian', '#ffff00'));
this.player.push(new Player('Janike', '#ff0000'));
this.field = new GameField(7, 6);
}
turn(x: number): void {
const currentPlayer = this.rotatePlayer();
const y = this.field.getBottomMostEmptyField(x);
if(y === null){
throw new Error(`Col in field at x ${x} is full.`);
}
this.field.setPlayerAt(x, y, currentPlayer);
const possibleWin = this.field.getPossibleWinSequenceAt(x, y, currentPlayer, this.minMatches);
if(possibleWin){
this.status = GameStatus.WON;
alert(currentPlayer.name + ' won!');
return;
}
if(this.field.isFull()){
this.status = GameStatus.FULL;
}
}
reset(){
this.field.clear();
this.playerTurnIndex = 0;
this.status = GameStatus.RUNNING;
}
rotatePlayer(): Player {
if(this.player.length === 0){
throw new Error('Playerlist is Empty');
}
if(this.playerTurnIndex + 1 >= this.player.length){
this.playerTurnIndex = 0;
} else {
this.playerTurnIndex = this.playerTurnIndex + 1;
}
return this.currentPlayer;
}
get currentPlayer(): Player {
if(this.player.length === 0){
throw new Error('Playerlist is Empty');
}
return this.player[this.playerTurnIndex];
}
}
class ConnectNFrontendCanvas {
private game: Game;
private ctx: CanvasRenderingContext2D;
private element: HTMLCanvasElement;
isRunning: boolean = true;
backgroundColor: Color = '#0000ff';
highlightColor: Color = '#4a4aff';
constructor(element: HTMLCanvasElement){
this.game = new Game();
this.element = element;
this.ctx = this.element.getContext('2d');
this.element.addEventListener('click', this.onClick.bind(this));
this.loop();
}
render(timestamp: number): void {
const {ctx} = this;
const colWidth = this.element.width / this.game.field.width;
const rowHeight = this.element.height / this.game.field.height;
const circleDiameter = Math.min(rowHeight, colWidth) - 20;
ctx.fillStyle = this.backgroundColor;
ctx.fillRect(0, 0, this.element.width, this.element.height);
this.game.field.iterate((field: Field, x: number, y: number) => {
const posX = x * colWidth + 0.5 * colWidth;
const posY = y * rowHeight + 0.5 * rowHeight;
ctx.fillStyle = this.highlightColor;
ctx.beginPath();
ctx.arc(posX, posY + 4, circleDiameter / 2 + 2, 0, 2 * Math.PI);
ctx.fill();
if(field){
ctx.fillStyle = field.color;
} else {
ctx.fillStyle = '#fff';
}
ctx.beginPath();
ctx.arc(posX, posY, circleDiameter / 2, 0, 2 * Math.PI);
ctx.fill();
});
}
loop(): void {
if(!this.isRunning){
return;
}
requestAnimationFrame(timestamp => {
this.tick(timestamp);
this.loop();
});
}
tick(timestamp: number): void {
this.render(timestamp);
}
private onClick(e: MouseEvent): void {
if(this.game.status !== GameStatus.RUNNING){
return;
}
const colWidth = this.element.clientWidth / this.game.field.width;
const x = Math.floor(e.x / colWidth);
this.game.turn(x);
}
}
let canvas = document.createElement('canvas');
document.body.appendChild(canvas);
document.body.style.margin = '0';
let x = new ConnectNFrontendCanvas(canvas);
let setSize = () => {
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
}
setSize();
document.addEventListener('resize', setSize);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment