Skip to content

Instantly share code, notes, and snippets.

@kajiiiro
Created April 13, 2024 05:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kajiiiro/1d07a0d66966fef11809c89206718106 to your computer and use it in GitHub Desktop.
Save kajiiiro/1d07a0d66966fef11809c89206718106 to your computer and use it in GitHub Desktop.
hanoi sample
const term = require('terminal-kit').terminal;
const TOWER_LABEL_START = 5; // 塔のラベル開始位置
const TOWER_SPACING = 15; // 塔間のスペース
const DISKS = [1, 2, 3];
const TOWERS = ['A', 'B', 'C'];
const INITIALIZE_TOWER = first(TOWERS);
const GOAL_TOWER = last(TOWERS);
const MINIMUM_MOVES = calculateMinimumMoves(DISKS.length);
const QUIT_WORDS = ['q', 'quit', 'exit', 'end'];
/**
* 塔のデータ構造はスタック(FILO)とする
* そのためにはディスクの大きい順にデータが生成されれば、
* ディスクの小さい順に取り出すことができる
*/
let towerState = Object.fromEntries(TOWERS.map(tower => {
if (tower === INITIALIZE_TOWER) {
return [tower, [...DISKS.reverse()]];
}
return [tower, []];
}));
let currentMoves = 0;
function calculateMinimumMoves(disks) {
return Math.pow(2, disks) - 1;
}
function first(array) {
return array[0];
}
function last(array) {
return array[array.length - 1];
}
function isEmpty(array) {
return array.length === 0;
}
function notEmpty(array) {
return array.length !== 0;
}
function alert(message) {
term.bgBlack().red(`\n${message}`);
}
function information(message) {
term.bgBlack().green(`\n${message}`);
}
function warning(message) {
term.bgBlack().yellow(`\n${message}`);
}
function wait(callback) {
setTimeout(callback, 700);
}
function quit() {
warning('プログラムを終了します。');
term.processExit();
}
function isGoal() {
return towerState[GOAL_TOWER].length === DISKS.length;
}
function isMinimumMoves() {
return MINIMUM_MOVES >= currentMoves;
}
function canStackDist(topDisk, bottomDisk) {
return topDisk < bottomDisk;
}
function printTowers() {
term.bgBlack().clear().bgBlack();
// 塔を表示
DISKS.forEach((_, i) => {
TOWERS.forEach((tower, j) => {
const disk = towerState[tower][DISKS.length - 1 - i] ?? ' ';
term.moveTo(TOWER_LABEL_START + TOWER_SPACING * j, i + 1)(`| ${disk} |`);
});
});
// 塔IDを表示
TOWERS.forEach((tower, index) => {
term.moveTo(TOWER_LABEL_START + TOWER_SPACING * index, DISKS.length + 1)(` ${tower} `);
});
// 最短移動数と現在移動数
term.moveTo(1, DISKS.length + 2).bold.magenta(`最短移動数: ${MINIMUM_MOVES} 現在移動数: ${currentMoves}`)
// 塔、塔ID、最短手の次の行から入力行とします
term.moveTo(1, DISKS.length + 3);
}
function moveDisk(from, to) {
if (isEmpty(towerState[from])) {
alert(`${from}塔にはディスクがありません!`);
return false;
}
if (notEmpty(towerState[to]) && !canStackDist(last(towerState[from]), last(towerState[to]))) {
alert(`無効な移動です!`);
return false;
}
towerState[to].push(towerState[from].pop());
currentMoves += 1;
return true;
}
function gameLoop() {
printTowers();
term.bgBlack().cyan("移動するディスクの塔と目的の塔を選んでください(例: AC): ")
term.inputField({}, (error, input) => {
if (QUIT_WORDS.includes(input.toLowerCase())) {
quit();
return;
}
const [from, to] = input.toUpperCase().split('');
if (!TOWERS.includes(from) || !TOWERS.includes(to)) {
alert('無効な入力です。もう一度試してください。');
wait(gameLoop);
return;
}
if (!moveDisk(from, to)) {
wait(gameLoop);
return;
}
if (!isGoal()) {
gameLoop();
return;
}
printTowers();
if (isMinimumMoves()) {
term.green('おめでとうございます!').red('\'最短移動数で\'').green('ディスクを移動しました。');
} else {
information('おめでとうございます!すべてのディスクを移動しました。');
}
term.processExit();
});
}
process.on('SIGINT', function () {
quit();
});
gameLoop();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment