...
This commit is contained in:
parent
19c5d5473f
commit
cc43ea8e0a
@ -60,7 +60,7 @@ dependencies {
|
||||
implementation ("org.commonmark:commonmark:0.18.0")
|
||||
implementation ("net.coobird:thumbnailator:0.4.14")
|
||||
|
||||
|
||||
implementation("org.sejda.imageio:webp-imageio:0.1.6")
|
||||
implementation ("com.drewnoakes:metadata-extractor:2.19.0")
|
||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||
compileOnly("org.projectlombok:lombok")
|
||||
|
||||
@ -75,7 +75,7 @@ class PuzzleController(
|
||||
/**
|
||||
* 스도쿠: (관리용) 새 퍼즐 문제 생성 및 DB 저장
|
||||
*/
|
||||
@GetMapping("/sudoku/generate")
|
||||
@GetMapping("/sudoku/sudoku_gen")
|
||||
suspend fun sudokuGenerateSinglePuzzle(): SudokuPuzzle {
|
||||
return puzzleService.sudoku_generateAndSavePuzzle()
|
||||
}
|
||||
@ -182,6 +182,16 @@ class PuzzleController(
|
||||
return vm
|
||||
}
|
||||
|
||||
/**
|
||||
* 스도쿠: 게임 페이지 서빙
|
||||
*/
|
||||
@GetMapping("/sudoku_gen.bs")
|
||||
suspend fun sudoku_gen(): ResultMV {
|
||||
val vm = ResultMV("content/puzzle/sudoku_gen")
|
||||
return vm
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 스파이더: 게임 페이지 서빙
|
||||
*/
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
//package kr.lunaticbum.back.lun.controllers
|
||||
//
|
||||
//import kr.lunaticbum.back.lun.model.Rank
|
||||
//import kr.lunaticbum.back.lun.model.RankRepository
|
||||
//import org.springframework.web.bind.annotation.*
|
||||
//import reactor.core.publisher.Flux
|
||||
//import reactor.core.publisher.Mono
|
||||
//
|
||||
//
|
||||
//@RestController
|
||||
//@RequestMapping("/rank")
|
||||
//class RankController(val rankRepository: RankRepository) {
|
||||
//// private val rankRepository: RankRepository
|
||||
////
|
||||
//// init {
|
||||
//// this.rankRepository = rankRepository
|
||||
//// }
|
||||
//
|
||||
// /**
|
||||
// * 새로운 랭킹을 저장합니다.
|
||||
// * 요청 Body에 gameId가 포함되어야 합니다.
|
||||
// * @param rank 저장할 랭크 정보 (gameId, name, score)
|
||||
// * @return Mono<Rank>
|
||||
// </Rank> */
|
||||
// @PostMapping("/ranks")
|
||||
// fun saveRank(@RequestBody rank: Rank): Mono<Rank?> { // 👈 요청 Body는 Rank 모델을 그대로 사용
|
||||
// return rankRepository.save(rank)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 특정 게임의 상위 10개 랭킹 리스트를 조회합니다.
|
||||
// * @param gameId 경로 변수(Path Variable)로 게임 ID를 받습니다.
|
||||
// * @return Flux<Rank>
|
||||
// </Rank> */
|
||||
// @GetMapping("/ranks/{gameId}") // 👈 엔드포인트에 Path Variable 추가
|
||||
// fun getRankingsByGameId(@PathVariable gameId: String): Flux<Rank?> {
|
||||
// return rankRepository.findTop10ByGameIdOrderByScoreDesc(gameId)
|
||||
// }
|
||||
//}
|
||||
@ -1,64 +0,0 @@
|
||||
//package kr.lunaticbum.back.lun.controllers
|
||||
//
|
||||
//import kr.lunaticbum.back.lun.model.SpiderGame
|
||||
//import kr.lunaticbum.back.lun.model.SpiderRank
|
||||
//import kr.lunaticbum.back.lun.model.SpiderService
|
||||
//import org.springframework.http.ResponseEntity
|
||||
//import org.springframework.web.bind.annotation.GetMapping
|
||||
//import org.springframework.web.bind.annotation.PathVariable
|
||||
//import org.springframework.web.bind.annotation.PostMapping
|
||||
//import org.springframework.web.bind.annotation.RequestBody
|
||||
//import org.springframework.web.bind.annotation.RequestMapping
|
||||
//import org.springframework.web.bind.annotation.RequestParam
|
||||
//import org.springframework.web.bind.annotation.RestController
|
||||
//import reactor.core.publisher.Flux
|
||||
//import reactor.core.publisher.Mono
|
||||
//
|
||||
//@RestController
|
||||
//@RequestMapping("/spider")
|
||||
//class SpiderController(private val spiderService: SpiderService,) {
|
||||
//
|
||||
// @GetMapping("/new")
|
||||
// fun newGame(@RequestParam numSuits: Int, @RequestParam numCards: String): Mono<SpiderGame> {
|
||||
// return spiderService.newGame(numSuits, numCards)
|
||||
// }
|
||||
//
|
||||
// @GetMapping("/{id}")
|
||||
// fun getGame(@PathVariable id: String): Mono<SpiderGame> {
|
||||
// return spiderService.getGame(id)
|
||||
// }
|
||||
//
|
||||
// @PostMapping("/update")
|
||||
// fun updateGame(@RequestBody game: SpiderGame): Mono<SpiderGame> {
|
||||
// return spiderService.updateGame(game)
|
||||
// }
|
||||
//
|
||||
// // 랭킹 등록 엔드포인트
|
||||
// @PostMapping("/register")
|
||||
// fun registerRank(@RequestBody rank: SpiderRank): Mono<ResponseEntity<SpiderRank>> {
|
||||
// return spiderService.registerRank(rank)
|
||||
// .map { savedRank -> ResponseEntity.ok(savedRank) }
|
||||
// .onErrorResume(IllegalArgumentException::class.java) { e ->
|
||||
// Mono.just(ResponseEntity.badRequest().body(null))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 게임 ID별 랭킹 조회 엔드포인트
|
||||
// @GetMapping("/list/{gameId}")
|
||||
// fun getRanks(@PathVariable gameId: String): Flux<SpiderRank> {
|
||||
// return spiderService.getRanksByGameId(gameId)
|
||||
// }
|
||||
//
|
||||
// @PostMapping("/deal")
|
||||
// fun dealCards(@RequestBody request: Map<String, String>): Mono<SpiderGame> {
|
||||
// val gameId = request["gameId"] ?: return Mono.error(IllegalArgumentException("Game ID is required."))
|
||||
// return spiderService.dealCardsFromStock(gameId)
|
||||
// }
|
||||
//
|
||||
// // 실행 취소 엔드포인트 추가
|
||||
// @PostMapping("/undo")
|
||||
// fun undo(@RequestBody request: Map<String, String>): Mono<SpiderGame> {
|
||||
// val gameId = request["gameId"] ?: return Mono.error(IllegalArgumentException("Game ID is required."))
|
||||
// return spiderService.undoGame(gameId)
|
||||
// }
|
||||
//}
|
||||
@ -1,26 +0,0 @@
|
||||
//package kr.lunaticbum.back.lun.controllers
|
||||
//import kr.lunaticbum.back.lun.model.GameRecord
|
||||
//import kr.lunaticbum.back.lun.model.SudokuPuzzle
|
||||
//import kr.lunaticbum.back.lun.model.SudokuService
|
||||
//import org.springframework.web.bind.annotation.*
|
||||
//
|
||||
//@RestController
|
||||
//@RequestMapping("/sudoku")
|
||||
//class SudokuController(private val sudokuService: SudokuService) {
|
||||
//
|
||||
// @GetMapping("/start")
|
||||
// suspend fun startGame(@RequestParam(defaultValue = "easy") difficulty: String): SudokuService.GameDto {
|
||||
// return sudokuService.startGame(difficulty)
|
||||
// }
|
||||
//
|
||||
// @PostMapping("/generate")
|
||||
// suspend fun generateSinglePuzzle(): SudokuPuzzle {
|
||||
// return sudokuService.generateAndSavePuzzle()
|
||||
// }
|
||||
//
|
||||
// @PostMapping("/validate")
|
||||
// suspend fun validate(@RequestBody validateDto: SudokuService.ValidateDto): Map<String, Boolean> {
|
||||
// val isCorrect = sudokuService.validateSolution(validateDto)
|
||||
// return mapOf("correct" to isCorrect)
|
||||
// }
|
||||
//}
|
||||
@ -165,6 +165,35 @@ interface PostRepository : ReactiveMongoRepository<Post, String> {
|
||||
"{ \$count: \"totalCount\" }" // 고유 그룹의 개수를 셈
|
||||
])
|
||||
fun countLatestUniqueOrigin(): Mono<AggregationCount> // 헬퍼 클래스로 매핑
|
||||
|
||||
|
||||
/**
|
||||
* 익명 사용자를 위한 '고유 최신 글' 목록을 페이지네이션으로 조회합니다.
|
||||
* [수정] posting이 true인 문서만 필터링하는 $match 단계를 추가합니다.
|
||||
*/
|
||||
@Aggregation(pipeline = [
|
||||
"{ \$sort: { modifyTime: -1 } }",
|
||||
"{ \$group: { _id: { \$ifNull: [\"\$originId\", \"\$_id\"] }, post: { \$first: \"\$\$ROOT\" } } }",
|
||||
"{ \$replaceRoot: { newRoot: \"\$post\" } }",
|
||||
// [[[[[ 신규 추가된 라인 ]]]]]
|
||||
"{ \$match: { posting: true } }",
|
||||
"{ \$sort: { \"modifyTime\": -1 } }"
|
||||
])
|
||||
fun findLatestUniquePublishedPaginated(pageable: Pageable): Flux<Post> // 메서드 이름 변경
|
||||
|
||||
/**
|
||||
* '고유 최신 글' 중 공개된 글의 총 개수를 카운트합니다.
|
||||
* [수정] posting이 true인 문서만 필터링하는 $match 단계를 추가합니다.
|
||||
*/
|
||||
@Aggregation(pipeline = [
|
||||
"{ \$sort: { modifyTime: -1 } }",
|
||||
"{ \$group: { _id: { \$ifNull: [\"\$originId\", \"\$_id\"] }, post: { \$first: \"\$\$ROOT\" } } }",
|
||||
"{ \$replaceRoot: { newRoot: \"\$post\" } }",
|
||||
// [[[[[ 신규 추가된 라인 ]]]]]
|
||||
"{ \$match: { posting: true } }",
|
||||
"{ \$count: \"totalCount\" }"
|
||||
])
|
||||
fun countLatestUniquePublished(): Mono<AggregationCount> // 메서드 이름 변경
|
||||
}
|
||||
|
||||
|
||||
@ -210,7 +239,7 @@ class PostManager(
|
||||
* [FIX]: This function should already be correct from the previous step.
|
||||
*/
|
||||
fun findLatestUniquePaginated(pageable: Pageable) : Mono<List<Post>> { // <-- Should already return Mono
|
||||
return postRepository.findLatestUniqueOriginPaginated(pageable)
|
||||
return postRepository.findLatestUniquePublishedPaginated(pageable)
|
||||
.collectList()
|
||||
}
|
||||
|
||||
@ -226,7 +255,7 @@ class PostManager(
|
||||
*/
|
||||
fun countLatestUnique(): Mono<Long> {
|
||||
// AggregationCount(totalCount=N) 객체에서 Long 값만 추출합니다. 결과가 없으면 0L을 반환합니다.
|
||||
return postRepository.countLatestUniqueOrigin()
|
||||
return postRepository.countLatestUniquePublished()
|
||||
.map { it.totalCount }
|
||||
.switchIfEmpty(Mono.just(0L))
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ import org.springframework.data.mongodb.core.index.Indexed
|
||||
* ======================================================
|
||||
*/
|
||||
|
||||
@Document("puzzles") // "puzzles" 컬렉션 (노노그램용)
|
||||
@Document("nonogram") // "puzzles" 컬렉션 (노노그램용)
|
||||
data class NonogramPuzzle(
|
||||
@Id
|
||||
val id: String? = null,
|
||||
@ -446,7 +446,7 @@ interface SpiderGameRepository : ReactiveMongoRepository<SpiderGame, String> {
|
||||
/**
|
||||
* 스도쿠 퍼즐 원본 데이터를 저장하는 모델
|
||||
*/
|
||||
@Document(collection = "puzzles") // (참고: 노노그램과 같은 컬렉션을 쓰지만 구조가 다름)
|
||||
@Document(collection = "Sudoku") // (참고: 노노그램과 같은 컬렉션을 쓰지만 구조가 다름)
|
||||
data class SudokuPuzzle(
|
||||
@Id
|
||||
val id: String? = null,
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
package kr.lunaticbum.back.lun.model
|
||||
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.mongodb.core.mapping.Document
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import reactor.core.publisher.Flux
|
||||
|
||||
|
||||
@Document(collection = "ranks")
|
||||
class Rank {
|
||||
// Getters and Setters
|
||||
@Id
|
||||
var id: String? = null
|
||||
|
||||
// 👈 Getter/Setter 추가
|
||||
var gameId: String? = null // 👈 게임 ID 필드 추가
|
||||
var name: String? = null
|
||||
var score: Int = 0
|
||||
|
||||
// Constructors
|
||||
constructor()
|
||||
|
||||
constructor(gameId: String?, name: String?, score: Int) {
|
||||
this.gameId = gameId
|
||||
this.name = name
|
||||
this.score = score
|
||||
}
|
||||
}
|
||||
|
||||
@Repository
|
||||
interface RankRepository : ReactiveMongoRepository<Rank, String> {
|
||||
/**
|
||||
* 특정 gameId에 대해 점수가 높은 순서대로 상위 10개의 랭킹을 조회합니다.
|
||||
* @param gameId 조회할 게임의 ID
|
||||
* @return Flux<Rank>
|
||||
</Rank> */
|
||||
// 쿼리 메소드 이름 변경 및 파라미터 추가
|
||||
fun findTop10ByGameIdOrderByScoreDesc(gameId: String): Flux<Rank?> // 👈 수정
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
//package kr.lunaticbum.back.lun.model
|
||||
//
|
||||
//import org.springframework.data.annotation.Id
|
||||
//import org.springframework.data.mongodb.core.mapping.Document
|
||||
//import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||
//import reactor.core.publisher.Mono
|
||||
//// (★ 삭제) Service, Flux, Random 관련 import 제거
|
||||
//
|
||||
//
|
||||
///*
|
||||
// * (★ 삭제됨) @Service class SpiderService (...)
|
||||
// * -> 모든 로직이 PuzzleService로 통합됨.
|
||||
// */
|
||||
//
|
||||
///* * (★ 삭제됨) data class SpiderRank (...)
|
||||
// * -> 통합 GameRank 모델로 대체됨.
|
||||
// */
|
||||
//
|
||||
///*
|
||||
// * (★ 삭제됨) interface SpiderRankRepository (...)
|
||||
// * -> 통합 GameRankRepository로 대체됨.
|
||||
// */
|
||||
@ -1,45 +0,0 @@
|
||||
//package kr.lunaticbum.back.lun.model
|
||||
//
|
||||
//// (★ 삭제) Service, Record 관련 import 모두 제거
|
||||
//import org.springframework.data.annotation.Id
|
||||
//import org.springframework.data.mongodb.core.index.Indexed
|
||||
//import org.springframework.data.mongodb.core.mapping.Document
|
||||
//import org.springframework.data.repository.kotlin.CoroutineCrudRepository
|
||||
//import org.springframework.stereotype.Repository
|
||||
//
|
||||
///**
|
||||
// * 스도쿠 퍼즐 원본 데이터를 저장하는 모델
|
||||
// */
|
||||
//@Document(collection = "puzzles") // (참고: 노노그램과 같은 컬렉션을 쓰지만 구조가 다름)
|
||||
//data class SudokuPuzzle(
|
||||
// @Id
|
||||
// val id: String? = null,
|
||||
// val puzzleKey: Long? = null,
|
||||
// @Indexed(unique = true)
|
||||
// val puzzle: String? // 81자리 완성된 퍼즐 데이터
|
||||
//)
|
||||
//
|
||||
///**
|
||||
// * 스도쿠 퍼즐 리포지토리 (PuzzleService에서 사용됨)
|
||||
// */
|
||||
//@Repository
|
||||
//interface SudokuPuzzleRepository : CoroutineCrudRepository<SudokuPuzzle, String> {
|
||||
// override suspend fun count(): Long
|
||||
// suspend fun findByPuzzleKey(puzzleKey: Long): SudokuPuzzle?
|
||||
// suspend fun findTopByOrderByPuzzleKeyDesc(): SudokuPuzzle?
|
||||
//}
|
||||
//
|
||||
//
|
||||
///* * (★ 삭제됨) data class GameRecord (...)
|
||||
// * -> 통합 GameRank 모델로 대체됨.
|
||||
// */
|
||||
//
|
||||
///*
|
||||
// * (★ 삭제됨) interface GameRecordRepository (...)
|
||||
// * -> 통합 GameRankRepository로 대체됨.
|
||||
// */
|
||||
//
|
||||
///*
|
||||
// * (★ 삭제됨) @Service class SudokuService (...)
|
||||
// * -> 모든 로직이 PuzzleService로 통합됨.
|
||||
// */
|
||||
@ -1,171 +0,0 @@
|
||||
/*!* =================================*/
|
||||
/* 기본 및 전체 레이아웃 (수정됨)*/
|
||||
/* ================================= *!*/
|
||||
/*body {*/
|
||||
/* !* (★ 삭제) font-family, text-align, background-color, color, margin, padding*/
|
||||
/* -> 이 속성들은 모두 common_game_theme.css에서 관리합니다.*/
|
||||
/* *!*/
|
||||
/* box-sizing: border-box;*/
|
||||
/*}*/
|
||||
|
||||
/*h1 {*/
|
||||
/* font-size: 15vw; !* 2048 고유의 큰 폰트 크기는 유지 *!*/
|
||||
/* margin: 20px 0;*/
|
||||
/* !* (★ 삭제) color 속성 삭제 -> common_game_theme에서 상속 *!*/
|
||||
/*}*/
|
||||
|
||||
/*.score-container {*/
|
||||
/* font-size: 24px;*/
|
||||
/* margin-bottom: 20px;*/
|
||||
/*}*/
|
||||
|
||||
/*!* =================================*/
|
||||
/* 게임 보드 (테마 적용)*/
|
||||
/* ================================= *!*/
|
||||
/*#game-board {*/
|
||||
/* display: grid;*/
|
||||
/* grid-template-columns: repeat(4, 1fr);*/
|
||||
/* grid-gap: 2vw;*/
|
||||
/* width: 95vw;*/
|
||||
/* max-width: 500px; !* (★ 수정) 400px -> 500px (다른 게임과 통일) *!*/
|
||||
/* margin: 0 auto;*/
|
||||
|
||||
/* !* (★ 수정) 2048의 갈색/베이지 테마를 차가운 회색/파란색 테마로 변경 *!*/
|
||||
/* background-color: #b0bec5; !* #bbada0 (갈색) -> #b0bec5 (블루 그레이) *!*/
|
||||
|
||||
/* padding: 2vw;*/
|
||||
/* border-radius: 6px;*/
|
||||
/* box-sizing: border-box;*/
|
||||
/* aspect-ratio: 1 / 1;*/
|
||||
/* touch-action: none;*/
|
||||
|
||||
/* !* (★ 추가) 공통 카드 UI와 유사한 그림자 효과 추가 *!*/
|
||||
/* box-shadow: 0 4px 10px rgba(0,0,0,0.08);*/
|
||||
/*}*/
|
||||
|
||||
/*@media (min-width: 481px) {*/
|
||||
/* #game-board {*/
|
||||
/* grid-gap: 10px;*/
|
||||
/* padding: 10px;*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
|
||||
|
||||
/*!* =================================*/
|
||||
/* 타일 공통 스타일 (테마 적용)*/
|
||||
/* ================================= *!*/
|
||||
/*.tile {*/
|
||||
/* width: 100%;*/
|
||||
/* height: 100%;*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: center;*/
|
||||
/* align-items: center;*/
|
||||
/* font-weight: bold;*/
|
||||
/* border-radius: 3px;*/
|
||||
|
||||
/* !* (★ 수정) 빈 타일 색상 변경 *!*/
|
||||
/* background-color: #eceff1; !* #cdc1b4 (갈색) -> #eceff1 (밝은 블루 그레이) *!*/
|
||||
|
||||
/* font-size: 5vw;*/
|
||||
/*}*/
|
||||
|
||||
/*@media (min-width: 481px) {*/
|
||||
/* .tile {*/
|
||||
/* font-size: 30px;*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
|
||||
/*!* =================================*/
|
||||
/* 타일 색상 (테마 적용)*/
|
||||
/* ================================= *!*/
|
||||
|
||||
/*!* (★ 수정) 2, 4 타일은 베이지색 계열이라 테마와 충돌하므로 파란색 계열로 변경 *!*/
|
||||
/*.tile-2 { background-color: #e3f2fd; color: #333; } !* #eee4da (베이지) -> #e3f2fd (밝은 파랑) *!*/
|
||||
/*.tile-4 { background-color: #bbdefb; color: #333; } !* #ede0c8 (노란 베이지) -> #bbdefb (파랑) *!*/
|
||||
|
||||
/*!* 8부터는 고유 색상이므로 유지 (새 테마와 잘 어울림) *!*/
|
||||
/*.tile-8 { background-color: #f2b179; color: #f9f6f2; }*/
|
||||
/*.tile-16 { background-color: #f59563; color: #f9f6f2; }*/
|
||||
/*.tile-32 { background-color: #f67c5f; color: #f9f6f2; }*/
|
||||
/*.tile-64 { background-color: #f65e3b; color: #f9f6f2; }*/
|
||||
/*.tile-128 { background-color: #edcf72; color: #f9f6f2; }*/
|
||||
/*.tile-256 { background-color: #edcc61; color: #f9f6f2; }*/
|
||||
/*.tile-512 { background-color: #edc850; color: #f9f6f2; }*/
|
||||
/*.tile-1024 { background-color: #edc53f; color: #f9f6f2; }*/
|
||||
/*.tile-2048 { background-color: #edc22e; color: #f9f6f2; }*/
|
||||
/*.tile-4096 { background-color: #3c3a32; color: #f9f6f2; }*/
|
||||
/*.tile-8192 { background-color: #ff3333; color: #f9f6f2; }*/
|
||||
/*.tile-16384 { background-color: #0077cc; color: #f9f6f2; }*/
|
||||
/*.tile-32768 { background-color: #9900cc; color: #f9f6f2; }*/
|
||||
|
||||
|
||||
/*!* =================================*/
|
||||
/* 게임 오버 팝업 (테마 적용)*/
|
||||
/* ================================= *!*/
|
||||
/*.popup-container {*/
|
||||
/* position: fixed;*/
|
||||
/* top: 0;*/
|
||||
/* left: 0;*/
|
||||
/* width: 100%;*/
|
||||
/* height: 100%;*/
|
||||
/* background-color: rgba(0, 0, 0, 0.5);*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: center;*/
|
||||
/* align-items: center;*/
|
||||
/* z-index: 100;*/
|
||||
/*}*/
|
||||
/*.popup {*/
|
||||
/* !* (★ 수정) 배경색을 테마에 맞게 흰색으로 변경 *!*/
|
||||
/* background-color: #ffffff; !* #faf8ef (베이지) -> #ffffff (흰색) *!*/
|
||||
/* padding: 20px;*/
|
||||
/* border-radius: 10px;*/
|
||||
/* text-align: center;*/
|
||||
/* width: 80vw;*/
|
||||
/* max-width: 300px;*/
|
||||
/* box-shadow: 0 4px 15px rgba(0,0,0,0.2); !* 흰색 배경이므로 그림자 추가 *!*/
|
||||
/*}*/
|
||||
/*.popup input {*/
|
||||
/* width: 100%;*/
|
||||
/* padding: 10px;*/
|
||||
/* margin: 10px 0;*/
|
||||
/* border: 1px solid #ccc;*/
|
||||
/* border-radius: 5px;*/
|
||||
/* box-sizing: border-box;*/
|
||||
/*}*/
|
||||
/*.popup button {*/
|
||||
/* padding: 10px 20px;*/
|
||||
/* !* (★ 삭제) background-color, color, border -> common_game_theme의 파란색 버튼 스타일을 상속받음 *!*/
|
||||
/* border-radius: 5px;*/
|
||||
/* cursor: pointer;*/
|
||||
/*}*/
|
||||
|
||||
/*!* =================================*/
|
||||
/* 랭킹 리스트 (테마 적용)*/
|
||||
/* ================================= *!*/
|
||||
/*.ranking-container {*/
|
||||
/* !**/
|
||||
/* (★ 참고) 이 컨테이너는 common_game_theme.css에서*/
|
||||
/* .game-card 스타일(흰색 배경, 그림자, 패딩)을 이미 적용받습니다.*/
|
||||
/* 여기서는 내부 정렬만 담당합니다.*/
|
||||
/* *!*/
|
||||
/* width: 100%;*/
|
||||
/* max-width: 500px; !* 공통 테마와 동일하게 설정 (중복 선언이지만 명확성을 위해 둠) *!*/
|
||||
/* margin: 30px auto;*/
|
||||
/* text-align: left;*/
|
||||
/*}*/
|
||||
/*.ranking-container h3 {*/
|
||||
/* text-align: center;*/
|
||||
/*}*/
|
||||
/*#ranking-list {*/
|
||||
/* list-style-type: none;*/
|
||||
/* padding: 0;*/
|
||||
/*}*/
|
||||
/*#ranking-list li {*/
|
||||
/* !* (★ 수정) 배경색 변경 *!*/
|
||||
/* background-color: #f0f4f8; !* #eee4da (베이지) -> #f0f4f8 (밝은 회색) *!*/
|
||||
/* margin-bottom: 5px;*/
|
||||
/* padding: 10px;*/
|
||||
/* border-radius: 5px;*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: space-between;*/
|
||||
/*}*/
|
||||
@ -45,15 +45,18 @@ body {
|
||||
font-family: var(--font-main);
|
||||
background-color: var(--color-bg-page);
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Create a new class for the game's specific layout */
|
||||
.game-body-wrapper {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* (★ 통일) H1 (게임 제목) 스타일 통일 (변수 사용) */
|
||||
h1 {
|
||||
font-size: clamp(2.2em, 8vw, 3.2em);
|
||||
|
||||
@ -1,314 +0,0 @@
|
||||
/*!* === nonogram.css (게임 플레이용) === *!*/
|
||||
/*body {*/
|
||||
/* !* (★ 삭제) font-family, align-items: center*/
|
||||
/* -> common_game_theme.css에서 관리합니다.*/
|
||||
/* *!*/
|
||||
/*}*/
|
||||
|
||||
/*#board-viewport {*/
|
||||
/* position: relative;*/
|
||||
/* width: 100%;*/
|
||||
/* max-width: 95vw; !* 화면 너비에 좀 더 맞춤 *!*/
|
||||
/* margin: 20px auto;*/
|
||||
/* !* (★ 핵심 수정) Flexbox로 자식 요소를 중앙 정렬합니다. *!*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: center; !* 자식 요소를 수평 중앙 정렬 *!*/
|
||||
/* align-items: flex-start; !* 위쪽에 정렬 *!*/
|
||||
/*}*/
|
||||
/*.reveal-img {*/
|
||||
/* position: absolute;*/
|
||||
/* top: 0;*/
|
||||
/* left: 0;*/
|
||||
/* width: 100%;*/
|
||||
/* height: 100%;*/
|
||||
/* opacity: 0; !* Hidden by default *!*/
|
||||
/* pointer-events: none; !* Make them unclickable *!*/
|
||||
/* transition: opacity 1.5s ease-in-out; !* Fade animation *!*/
|
||||
/* transform-origin: top left; !* Align with the game board's scaling *!*/
|
||||
/*}*/
|
||||
|
||||
/*.guide-line-right {*/
|
||||
/* border-right: 2px solid #999 !important;*/
|
||||
/*}*/
|
||||
|
||||
/*.guide-line-bottom {*/
|
||||
/* border-bottom: 2px solid #999 !important;*/
|
||||
/*}*/
|
||||
|
||||
/*#game-board {*/
|
||||
/* display: grid;*/
|
||||
/* gap: 1px;*/
|
||||
/* background-color: #999;*/
|
||||
/* border: 2px solid #333;*/
|
||||
/* transform-origin: top;*/
|
||||
/*}*/
|
||||
/*#game-controls {*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: space-between;*/
|
||||
/* align-items: center;*/
|
||||
/* width: 100%;*/
|
||||
/* !* (★ 삭제) 아래 속성들은 common_game_theme.css의 #game-controls 셀렉터가 관리합니다.*/
|
||||
/* max-width: 500px;*/
|
||||
/* margin-bottom: 15px;*/
|
||||
/* *!*/
|
||||
/* font-size: 1.2em;*/
|
||||
/* flex-wrap: wrap;*/
|
||||
/* gap: 15px;*/
|
||||
|
||||
/* !* (★ 참고) common_game_theme에서 이미 background: white, padding, box-shadow 등이 적용된 상태입니다.*/
|
||||
/* 이 CSS는 그 내부의 flex 정렬만 담당하게 됩니다.*/
|
||||
/* *!*/
|
||||
/*}*/
|
||||
|
||||
/*#mode-selector {*/
|
||||
/* display: flex;*/
|
||||
/* gap: 5px;*/
|
||||
/* border: 1px solid #ccc;*/
|
||||
/* border-radius: 8px;*/
|
||||
/* padding: 4px;*/
|
||||
/* background-color: #f0f0f0;*/
|
||||
/*}*/
|
||||
|
||||
/*#mode-selector label {*/
|
||||
/* cursor: pointer;*/
|
||||
/* user-select: none;*/
|
||||
/*}*/
|
||||
|
||||
/*#mode-selector span {*/
|
||||
/* padding: 8px 15px;*/
|
||||
/* border-radius: 5px;*/
|
||||
/* display: block;*/
|
||||
/* transition: background-color 0.2s, color 0.2s;*/
|
||||
/*}*/
|
||||
|
||||
/*#mode-selector input[type="radio"] {*/
|
||||
/* display: none;*/
|
||||
/*}*/
|
||||
|
||||
/*#mode-selector input[type="radio"]:checked + span {*/
|
||||
/* background-color: #007bff; !* (★ 참고) 공통 테마의 파란색과 동일하므로 유지 *!*/
|
||||
/* color: white;*/
|
||||
/* box-shadow: inset 0 1px 3px rgba(0,0,0,0.2);*/
|
||||
/*}*/
|
||||
/*#hint-btn {*/
|
||||
/* padding: 8px 15px;*/
|
||||
/* font-weight: bold;*/
|
||||
/* cursor: pointer;*/
|
||||
/* border-radius: 5px;*/
|
||||
/* border: 1px solid #ccc;*/
|
||||
/*}*/
|
||||
/*#hint-btn:disabled {*/
|
||||
/* cursor: not-allowed;*/
|
||||
/* opacity: 0.5;*/
|
||||
/*}*/
|
||||
|
||||
/*.col-clues-container, .row-clues-container {*/
|
||||
/* display: flex;*/
|
||||
/*}*/
|
||||
/*.row-clues-container {*/
|
||||
/* flex-direction: column;*/
|
||||
/*}*/
|
||||
|
||||
/*.puzzle-grid-container {*/
|
||||
/* display: grid;*/
|
||||
/* border: 2px solid #333;*/
|
||||
/*}*/
|
||||
|
||||
/*!* nonogram.css의 .clue-cell (게임용) *!*/
|
||||
/*.clue-cell {*/
|
||||
/* background-color: #f0f0f0;*/
|
||||
/* font-weight: bold;*/
|
||||
/* font-size: 14px;*/
|
||||
/* box-sizing: border-box;*/
|
||||
/* display: flex;*/
|
||||
/* padding: 5px;*/
|
||||
/*}*/
|
||||
|
||||
/*.row-clue {*/
|
||||
/* justify-content: flex-end; !* 힌트 오른쪽 정렬 *!*/
|
||||
/* align-items: center;*/
|
||||
/*}*/
|
||||
|
||||
/*.col-clue {*/
|
||||
/* justify-content: center; !* 힌트 가운데 정렬 *!*/
|
||||
/* align-items: flex-end; !* 힌트 아래쪽 정렬 *!*/
|
||||
/* text-align: center;*/
|
||||
/* line-height: 1.2; !* 줄 간격 *!*/
|
||||
/*}*/
|
||||
|
||||
/*!* nonogram.css의 .grid-cell (게임용) *!*/
|
||||
/*.grid-cell {*/
|
||||
/* background-color: #fff;*/
|
||||
/* border: 1px solid #ddd;*/
|
||||
/* box-sizing: border-box;*/
|
||||
/* cursor: pointer;*/
|
||||
/*}*/
|
||||
|
||||
/*!* nonogram.css의 .filled (게임용) *!*/
|
||||
/*.grid-cell.filled {*/
|
||||
/* background-color: #333;*/
|
||||
/*}*/
|
||||
|
||||
/*.grid-cell.marked::after {*/
|
||||
/* content: 'X';*/
|
||||
/* color: #ff5c5c;*/
|
||||
/* font-weight: bold;*/
|
||||
/* font-size: 1.2em;*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: center;*/
|
||||
/* align-items: center;*/
|
||||
/* width: 100%;*/
|
||||
/* height: 100%;*/
|
||||
/*}*/
|
||||
|
||||
/*.grid-cell.incorrect {*/
|
||||
/* background-color: #ffcccc;*/
|
||||
/* animation: shake 0.5s;*/
|
||||
/*}*/
|
||||
/*@keyframes shake {*/
|
||||
/* 0%, 100% { transform: translateX(0); }*/
|
||||
/* 25% { transform: translateX(-5px); }*/
|
||||
/* 75% { transform: translateX(5px); }*/
|
||||
/*}*/
|
||||
|
||||
|
||||
/*#result-overlay {*/
|
||||
/* position: fixed; !* 화면 전체에 고정 *!*/
|
||||
/* top: 0;*/
|
||||
/* left: 0;*/
|
||||
/* width: 100vw; !* 뷰포트 너비 100% *!*/
|
||||
/* height: 100vh; !* 뷰포트 높이 100% *!*/
|
||||
/* background-color: rgba(0, 0, 0, 0.75); !* 반투명 검은 배경 *!*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: center;*/
|
||||
/* align-items: center;*/
|
||||
/* z-index: 100;*/
|
||||
/* opacity: 0;*/
|
||||
/* pointer-events: none;*/
|
||||
/* transition: opacity 0.3s ease-in-out;*/
|
||||
/*}*/
|
||||
/*#result-overlay.visible {*/
|
||||
/* opacity: 1;*/
|
||||
/* pointer-events: auto;*/
|
||||
/*}*/
|
||||
/*#result-modal {*/
|
||||
/* background-color: white;*/
|
||||
/* padding: 20px 40px;*/
|
||||
/* border-radius: 10px;*/
|
||||
/* text-align: center;*/
|
||||
/* box-shadow: 0 5px 15px rgba(0,0,0,0.3);*/
|
||||
/*}*/
|
||||
/*#modal-title {*/
|
||||
/* margin-top: 0;*/
|
||||
/* font-size: 2.5em;*/
|
||||
/*}*/
|
||||
/*#modal-buttons button {*/
|
||||
/* padding: 10px 20px;*/
|
||||
/* margin: 0 10px;*/
|
||||
/* font-size: 1em;*/
|
||||
/* cursor: pointer;*/
|
||||
/* border-radius: 5px;*/
|
||||
/* border: 1px solid #ccc;*/
|
||||
/* min-width: 120px;*/
|
||||
/*}*/
|
||||
/*#modal-buttons button.primary {*/
|
||||
/* background-color: #4CAF50;*/
|
||||
/* color: white;*/
|
||||
/* border-color: #4CAF50;*/
|
||||
/*}*/
|
||||
|
||||
/*.hidden {*/
|
||||
/* display: none;*/
|
||||
/*}*/
|
||||
|
||||
/*.clue-cell.completed {*/
|
||||
/* color: #999; !* 색상을 회색으로 *!*/
|
||||
/* text-decoration: line-through; !* 취소선 *!*/
|
||||
/*}*/
|
||||
|
||||
/*.grid-cell.locked {*/
|
||||
/* opacity: 0.8; !* 약간 투명하게 *!*/
|
||||
/*}*/
|
||||
|
||||
/*.grid-cell.selecting {*/
|
||||
/* background-color: rgba(0, 123, 255, 0.3); !* 반투명 파란색 배경 *!*/
|
||||
/* border-color: rgba(0, 123, 255, 0.5);*/
|
||||
/*}*/
|
||||
|
||||
|
||||
/*!* === puzzle.css (업로드 미리보기용) - 충돌 해결됨 === *!*/
|
||||
|
||||
/*#puzzle-container {*/
|
||||
/* display: grid;*/
|
||||
/* !* We will set grid-template-columns/rows with JS *!*/
|
||||
/* grid-gap: 2px;*/
|
||||
/* margin-top: 20px;*/
|
||||
/* background-color: #333;*/
|
||||
/* border: 2px solid #333;*/
|
||||
/* width: fit-content;*/
|
||||
/*}*/
|
||||
|
||||
/*!* (★충돌 해결) #puzzle-container 내부의 .grid-cell (미리보기 코너 셀) *!*/
|
||||
/*#puzzle-container .grid-cell {*/
|
||||
/* width: 25px;*/
|
||||
/* height: 25px;*/
|
||||
/* background-color: #f0f0f0;*/
|
||||
/* text-align: center;*/
|
||||
/* line-height: 25px;*/
|
||||
/* font-size: 14px;*/
|
||||
/* !* nonogram.css의 .grid-cell 스타일과 겹치지 않음 *!*/
|
||||
/* cursor: default;*/
|
||||
/* border: none;*/
|
||||
/*}*/
|
||||
|
||||
/*!* (★충돌 해결) #puzzle-container 내부의 .clue-cell (미리보기 힌트 셀) *!*/
|
||||
/*#puzzle-container .clue-cell {*/
|
||||
/* background-color: #cce7ff;*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: center;*/
|
||||
/* align-items: center;*/
|
||||
/* padding: 5px;*/
|
||||
/* min-height: 25px;*/
|
||||
/* font-weight: bold;*/
|
||||
/* !* nonogram.css의 .clue-cell 스타일과 겹치지 않음 *!*/
|
||||
/* font-size: 14px;*/
|
||||
/*}*/
|
||||
|
||||
/*.solution-cell {*/
|
||||
/* width: 25px;*/
|
||||
/* height: 25px;*/
|
||||
/*}*/
|
||||
|
||||
/*!* (★충돌 해결) .filled 대신 .solution-cell.filled 사용 *!*/
|
||||
/*.solution-cell.filled {*/
|
||||
/* background-color: #333;*/
|
||||
/*}*/
|
||||
|
||||
/*!* .empty는 .solution-cell.empty로 사용 (upload.js 기준) *!*/
|
||||
/*.solution-cell.empty {*/
|
||||
/* background-color: #fff;*/
|
||||
/*}*/
|
||||
|
||||
/*#puzzle-wrapper {*/
|
||||
/* position: relative; !* Needed for absolute positioning of children *!*/
|
||||
/*}*/
|
||||
|
||||
/*!* 이 ID는 nonogram.html에서 사용되지 않으므로 충돌 없음 *!*/
|
||||
/*#success-animation-container {*/
|
||||
/* position: absolute;*/
|
||||
/* top: 0;*/
|
||||
/* left: 0;*/
|
||||
/* width: 100%;*/
|
||||
/* height: 100%;*/
|
||||
/* pointer-events: none; !* Allows clicking through the container *!*/
|
||||
/*}*/
|
||||
|
||||
/*#success-animation-container img {*/
|
||||
/* position: absolute;*/
|
||||
/* top: 0;*/
|
||||
/* left: 0;*/
|
||||
/* width: 100%;*/
|
||||
/* height: 100%;*/
|
||||
/* opacity: 0; !* Hidden by default *!*/
|
||||
/* transition: opacity 1.0s ease-in-out; !* Fade animation *!*/
|
||||
/*}*/
|
||||
@ -1,41 +0,0 @@
|
||||
/*!* (★ 수정) #game-container가 전체 화면(100vw/vh)을 차지하는 대신,*/
|
||||
/* common_game_theme의 body(#f4f7f9 배경) 위에 떠 있는*/
|
||||
/* '게임 테이블(카드)' 역할을 하도록 변경합니다. *!*/
|
||||
/*#game-container {*/
|
||||
/* !* (★ 남김) 내부 캔버스를 정렬하는 로직은 유지 *!*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: center;*/
|
||||
/* align-items: flex-start;*/
|
||||
|
||||
/* !* (★ 유지/수정) '펠트' 배경색은 유지하되, 공통 '카드' UI 요소를 추가 *!*/
|
||||
/* background-color: #008000;*/
|
||||
/* border-radius: 8px; !* (★ 추가) 공통 테마 둥근 모서리 *!*/
|
||||
/* box-shadow: 0 4px 10px rgba(0,0,0,0.08); !* (★ 추가) 공통 테마 그림자 *!*/
|
||||
/* padding: 15px; !* (★ 추가) 캔버스 주변 여백 *!*/
|
||||
/* box-sizing: border-box;*/
|
||||
|
||||
/* !* (★ 삭제) 100vw, 100vh 속성을 삭제하여 body의 중앙 정렬이 동작하도록 함 *!*/
|
||||
/* !* width: 100vw; (삭제) *!*/
|
||||
/* !* height: 100vh; (삭제) *!*/
|
||||
|
||||
/* !* (★ 수정) 너비 관리: 다른 게임(500px)보다 넓은 반응형 최대 너비를 가짐 *!*/
|
||||
/* width: 95%; !* 뷰포트의 95%를 사용 *!*/
|
||||
/* max-width: 1200px; !* 단, 스파이더 게임에 맞게 1200px까지 허용 *!*/
|
||||
/*}*/
|
||||
|
||||
/*#gameCanvas {*/
|
||||
/* !* (★ 삭제) 컨테이너가 이미 녹색이므로 캔버스 자체의 배경은 불필요 *!*/
|
||||
/* !* background-color: #008000; (삭제) *!*/
|
||||
|
||||
/* !* (★ 수정) 흰색 테두리보다 펠트 색과 대비되는 어두운 테두리로 변경 *!*/
|
||||
/* border: 2px solid #004d00; !* #fff (흰색) -> #004d00 (어두운 녹색) *!*/
|
||||
|
||||
/* !* (★ 수정) 너비: 부모(#game-container) 패딩 영역의 100%를 차지 *!*/
|
||||
/* width: 100%;*/
|
||||
/* height: auto; !* 너비에 맞춰 캔버스 비율(JS가 설정한)을 따름 *!*/
|
||||
|
||||
/* !* (★ 삭제) 뷰포트 기준 max-height 삭제. 컨테이너 너비와 캔버스 비율로 크기가 결정됨. *!*/
|
||||
/* !* max-height: min(95vw, 95vh); (삭제) *!*/
|
||||
|
||||
/* box-sizing: border-box; !* 유지 *!*/
|
||||
/*}*/
|
||||
@ -1,260 +0,0 @@
|
||||
/*!* 기본 스타일 초기화 및 폰트 설정 *!*/
|
||||
/*body {*/
|
||||
/* !**/
|
||||
/* (★ 삭제) 아래 속성들은 common_game_theme.css에서 관리합니다.*/
|
||||
/* font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;*/
|
||||
/* margin: 0;*/
|
||||
/* padding: 20px;*/
|
||||
/* background-color: #f4f7f9;*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: center;*/
|
||||
/* min-height: 100vh;*/
|
||||
/* *!*/
|
||||
/*}*/
|
||||
|
||||
/*#sudoku-game-app {*/
|
||||
/* width: 100%;*/
|
||||
/* margin: 20px 0;*/
|
||||
/*}*/
|
||||
|
||||
/*.container {*/
|
||||
/* !**/
|
||||
/* (★ 삭제) 아래 속성들은 common_game_theme.css에서*/
|
||||
/* '#sudoku-game-app .container' 셀렉터로 이미 관리하고 있습니다.*/
|
||||
|
||||
/* background: white;*/
|
||||
/* padding: clamp(15px, 4vw, 30px);*/
|
||||
/* border-radius: 8px;*/
|
||||
/* box-shadow: 0 4px 10px rgba(0,0,0,0.1);*/
|
||||
/* text-align: center;*/
|
||||
/* max-width: 500px;*/
|
||||
/* width: 100%;*/
|
||||
/* box-sizing: border-box;*/
|
||||
/* margin: 0 auto;*/
|
||||
/* *!*/
|
||||
|
||||
/* !* (★ 남김) .container에만 필요한 고유 속성 (text-align)은 남겨두거나 common_game_theme로 이동 *!*/
|
||||
/* text-align: center;*/
|
||||
/*}*/
|
||||
|
||||
/*h1 {*/
|
||||
/* font-size: 1.8em;*/
|
||||
/* color: #333;*/
|
||||
/* margin-top: 0;*/
|
||||
/* margin-bottom: 20px;*/
|
||||
/* !* (★ 참고) h1은 common_game_theme의 스타일을 상속받습니다.*/
|
||||
/* 만약 스도쿠만 다른 스타일을 원한다면 여기에서 재정의(override)하면 됩니다.*/
|
||||
/* 현재는 공통 스타일이 적용됩니다. *!*/
|
||||
/*}*/
|
||||
|
||||
/*!* (★ 삭제) 아래의 'button' 공통 스타일은 common_game_theme.css가 처리합니다. *!*/
|
||||
/*!**/
|
||||
/*button {*/
|
||||
/* padding: 10px 20px;*/
|
||||
/* font-size: 1em;*/
|
||||
/* cursor: pointer;*/
|
||||
/* background-color: #007bff;*/
|
||||
/* color: white;*/
|
||||
/* border: none;*/
|
||||
/* border-radius: 5px;*/
|
||||
/* transition: background-color 0.2s;*/
|
||||
/*}*/
|
||||
/*button:hover:not(:disabled) {*/
|
||||
/* background-color: #0056b3;*/
|
||||
/*}*/
|
||||
/*button:disabled {*/
|
||||
/* background-color: #cccccc;*/
|
||||
/* cursor: not-allowed;*/
|
||||
/*}*/
|
||||
/**!*/
|
||||
|
||||
|
||||
/*!* ======================================= *!*/
|
||||
/*!* (★ 남김) 아래부터는 스도쿠 고유의 스타일입니다. (수정 불필요) *!*/
|
||||
/*!* ======================================= *!*/
|
||||
|
||||
/*!* 게임 컨테이너 *!*/
|
||||
/*#game-container {*/
|
||||
/* display: flex;*/
|
||||
/* flex-direction: column;*/
|
||||
/* align-items: center;*/
|
||||
/* max-width: 500px;*/
|
||||
/* margin: 0 auto;*/
|
||||
/*}*/
|
||||
|
||||
/*!* 게임 정보 (점수, 타이머) *!*/
|
||||
/*.game-info {*/
|
||||
/* width: 100%;*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: space-between;*/
|
||||
/* align-items: center;*/
|
||||
/* margin-bottom: 15px;*/
|
||||
/* padding: 0 10px;*/
|
||||
/* box-sizing: border-box;*/
|
||||
/* font-size: 1.5em;*/
|
||||
/* font-weight: bold;*/
|
||||
/*}*/
|
||||
/*#score { color: #007bff; }*/
|
||||
/*#timer { color: #333; }*/
|
||||
|
||||
/*!* 스도쿠 보드 *!*/
|
||||
/*#sudoku-board {*/
|
||||
/* display: grid;*/
|
||||
/* grid-template-columns: repeat(9, 1fr);*/
|
||||
/* grid-template-rows: repeat(9, 1fr);*/
|
||||
/* width: 100%;*/
|
||||
/* border: 3px solid #333;*/
|
||||
/* aspect-ratio: 1 / 1;*/
|
||||
/*}*/
|
||||
|
||||
/*.cell {*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: center;*/
|
||||
/* align-items: center;*/
|
||||
/* font-size: clamp(1em, 4vw, 1.8em);*/
|
||||
/* font-weight: bold;*/
|
||||
/* color: #333;*/
|
||||
/* border: 1px solid #ddd;*/
|
||||
/* box-sizing: border-box;*/
|
||||
/* cursor: pointer;*/
|
||||
/*}*/
|
||||
|
||||
/*.cell:nth-child(3n) { border-right: 2px solid #333; }*/
|
||||
/*.cell:nth-child(9n) { border-right-width: 1px; }*/
|
||||
/*.cell:nth-child(n+19):nth-child(-n+27),*/
|
||||
/*.cell:nth-child(n+46):nth-child(-n+54) {*/
|
||||
/* border-bottom: 2px solid #333;*/
|
||||
/*}*/
|
||||
|
||||
/*.cell:not(.editable) {*/
|
||||
/* background-color: #f0f0f0;*/
|
||||
/* color: #222;*/
|
||||
/* cursor: default;*/
|
||||
/*}*/
|
||||
|
||||
/*!* 하이라이트 & 오답 스타일 *!*/
|
||||
/*.cell.incorrect {*/
|
||||
/* background-color: #ffdddd !important;*/
|
||||
/* color: #d8000c !important;*/
|
||||
/*}*/
|
||||
/*.highlight-focused {*/
|
||||
/* background-color: #dbeeff !important;*/
|
||||
/*}*/
|
||||
/*.highlight-same-number {*/
|
||||
/* background-color: #e6e6e6 !important;*/
|
||||
/*}*/
|
||||
/*.highlight-selected-number {*/
|
||||
/* background-color: #b3d7ff !important;*/
|
||||
/*}*/
|
||||
|
||||
/*!* 숫자 입력 버튼 *!*/
|
||||
/*#number-input-buttons {*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: space-between;*/
|
||||
/* width: 100%;*/
|
||||
/* margin-top: 15px;*/
|
||||
/* gap: 1%;*/
|
||||
/*}*/
|
||||
|
||||
/*#number-input-buttons .num-btn,*/
|
||||
/*#number-input-buttons #undo-btn {*/
|
||||
/* line-height: unset;*/
|
||||
/* min-width: unset;*/
|
||||
/* width: 9%;*/
|
||||
/* aspect-ratio: 1/1;*/
|
||||
/* font-size: clamp(1em, 4vw, 1.8em);*/
|
||||
/* font-weight: bold;*/
|
||||
/* border-radius: 8px;*/
|
||||
/* background-color: #f0f0f0;*/
|
||||
/* color: #333;*/
|
||||
/* border: 1px solid #ccc;*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: center;*/
|
||||
/* align-items: center;*/
|
||||
/* cursor: pointer;*/
|
||||
/* padding: 0;*/
|
||||
/* transition: background-color 0.2s, transform 0.1s, opacity 0.2s;*/
|
||||
/*}*/
|
||||
|
||||
/*#number-input-buttons .num-btn.selected {*/
|
||||
/* background-color: #007bff;*/
|
||||
/* color: white;*/
|
||||
/* border-color: #007bff;*/
|
||||
/*}*/
|
||||
|
||||
/*#number-input-buttons .num-btn.completed {*/
|
||||
/* opacity: 0.4;*/
|
||||
/* background-color: #e9ecef;*/
|
||||
/* pointer-events: none;*/
|
||||
/*}*/
|
||||
|
||||
/*#number-input-buttons #undo-btn {*/
|
||||
/* background-color: #f8f9fa;*/
|
||||
/* color: #dc3545;*/
|
||||
/*}*/
|
||||
|
||||
/*!* 액션 버튼 (힌트, 정답확인) *!*/
|
||||
/*.action-buttons {*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: center;*/
|
||||
/* gap: 10px;*/
|
||||
/* margin-top: 15px;*/
|
||||
/* width: 100%;*/
|
||||
/*}*/
|
||||
/*.action-buttons button {*/
|
||||
/* flex-grow: 1;*/
|
||||
/* max-width: 200px;*/
|
||||
/*}*/
|
||||
|
||||
/*!* 모달 및 숨김 처리 *!*/
|
||||
/*.hidden {*/
|
||||
/* display: none !important;*/
|
||||
/*}*/
|
||||
/*#modal-overlay, #game-over-modal {*/
|
||||
/* position: fixed;*/
|
||||
/* top: 0; left: 0;*/
|
||||
/* width: 100%; height: 100%;*/
|
||||
/* background-color: rgba(0,0,0,0.7);*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: center;*/
|
||||
/* align-items: center;*/
|
||||
/* z-index: 1000;*/
|
||||
/*}*/
|
||||
/*#modal-content {*/
|
||||
/* background: white;*/
|
||||
/* padding: 30px;*/
|
||||
/* border-radius: 10px;*/
|
||||
/* text-align: center;*/
|
||||
/* width: 90%;*/
|
||||
/* max-width: 400px;*/
|
||||
/* box-shadow: 0 8px 20px rgba(0,0,0,0.2);*/
|
||||
/*}*/
|
||||
/*#modal-content h2, #modal-content h3 {*/
|
||||
/* color: #333;*/
|
||||
/* margin-bottom: 15px;*/
|
||||
/*}*/
|
||||
/*#username-input {*/
|
||||
/* width: calc(100% - 24px);*/
|
||||
/* padding: 10px;*/
|
||||
/* margin-bottom: 15px;*/
|
||||
/* border: 1px solid #ccc;*/
|
||||
/* border-radius: 5px;*/
|
||||
/* font-size: 1em;*/
|
||||
/*}*/
|
||||
/*#ranking-list {*/
|
||||
/* list-style-type: decimal;*/
|
||||
/* list-style-position: inside;*/
|
||||
/* padding: 0;*/
|
||||
/* text-align: left;*/
|
||||
/* margin-top: 20px;*/
|
||||
/*}*/
|
||||
/*#ranking-list li {*/
|
||||
/* padding: 8px 0;*/
|
||||
/* border-bottom: 1px solid #eee;*/
|
||||
/* display: flex;*/
|
||||
/* justify-content: space-between;*/
|
||||
/* align-items: center;*/
|
||||
/*}*/
|
||||
/*#ranking-list li:last-child {*/
|
||||
/* border-bottom: none;*/
|
||||
/*}*/
|
||||
@ -1,238 +0,0 @@
|
||||
// document.addEventListener('DOMContentLoaded', () => {
|
||||
// // ... (DOM 요소 가져오기 - 동일)
|
||||
// const gameBoard = document.getElementById('game-board');
|
||||
// const scoreDisplay = document.getElementById('score');
|
||||
// const gameOverPopup = document.getElementById('game-over-popup');
|
||||
// const finalScoreDisplay = document.getElementById('final-score');
|
||||
// const playerNameInput = document.getElementById('player-name');
|
||||
// const saveScoreButton = document.getElementById('save-score');
|
||||
// const rankingList = document.getElementById('ranking-list');
|
||||
//
|
||||
// // (★ 수정) 게임 ID 대신, 공통 Enum 타입 문자열 사용
|
||||
// const currentGameType = 'GAME_2048'; // (GameType.GAME_2048과 일치)
|
||||
// const currentContextId = null; // 2048은 별도 컨텍스트 ID가 없음
|
||||
//
|
||||
// let gridSize = 4;
|
||||
// let board = [];
|
||||
// let score = 0;
|
||||
// let touchStartX = 0, touchStartY = 0, touchEndX = 0, touchEndY = 0;
|
||||
//
|
||||
// // ----- 게임 핵심 로직 -----
|
||||
// function initializeBoard() {
|
||||
// gameBoard.innerHTML = ''; // 기존 타일 초기화
|
||||
// for (let i = 0; i < gridSize * gridSize; i++) {
|
||||
// const tile = document.createElement('div');
|
||||
// tile.className = 'tile';
|
||||
// gameBoard.appendChild(tile);
|
||||
// }
|
||||
// board = Array(gridSize * gridSize).fill(0);
|
||||
// addNumber();
|
||||
// addNumber();
|
||||
// updateBoard();
|
||||
// }
|
||||
//
|
||||
// function updateBoard() {
|
||||
// const tiles = gameBoard.children;
|
||||
// for (let i = 0; i < board.length; i++) {
|
||||
// const value = board[i];
|
||||
// const tile = tiles[i];
|
||||
// tile.textContent = value === 0 ? '' : value;
|
||||
// tile.className = 'tile' + (value > 0 ? ' tile-' + value : '');
|
||||
// }
|
||||
// scoreDisplay.textContent = score;
|
||||
// }
|
||||
//
|
||||
// function addNumber() {
|
||||
// const available = board.map((val, i) => val === 0 ? i : -1).filter(i => i !== -1);
|
||||
// if (available.length > 0) {
|
||||
// const spot = available[Math.floor(Math.random() * available.length)];
|
||||
// board[spot] = Math.random() < 0.9 ? 2 : 4;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // ----- 타일 이동 및 병합 로직 -----
|
||||
// function moveRow(row) {
|
||||
// let arr = row.filter(val => val);
|
||||
// for (let i = 0; i < arr.length - 1; i++) {
|
||||
// if (arr[i] === arr[i + 1]) {
|
||||
// arr[i] *= 2;
|
||||
// score += arr[i];
|
||||
// arr[i + 1] = 0;
|
||||
// }
|
||||
// }
|
||||
// arr = arr.filter(val => val);
|
||||
// const missing = gridSize - arr.length;
|
||||
// const zeros = Array(missing).fill(0);
|
||||
// return arr.concat(zeros);
|
||||
// }
|
||||
//
|
||||
// function moveLeft() {
|
||||
// let changed = false;
|
||||
// for (let i = 0; i < gridSize; i++) {
|
||||
// const rowStart = i * gridSize;
|
||||
// const row = board.slice(rowStart, rowStart + gridSize);
|
||||
// const newRow = moveRow(row);
|
||||
// if (JSON.stringify(row) !== JSON.stringify(newRow)) changed = true;
|
||||
// board.splice(rowStart, gridSize, ...newRow);
|
||||
// }
|
||||
// return changed;
|
||||
// }
|
||||
//
|
||||
// function moveRight() {
|
||||
// let changed = false;
|
||||
// for (let i = 0; i < gridSize; i++) {
|
||||
// const rowStart = i * gridSize;
|
||||
// const row = board.slice(rowStart, rowStart + gridSize).reverse();
|
||||
// const newRow = moveRow(row).reverse();
|
||||
// if (JSON.stringify(board.slice(rowStart, rowStart + gridSize)) !== JSON.stringify(newRow)) changed = true;
|
||||
// board.splice(rowStart, gridSize, ...newRow);
|
||||
// }
|
||||
// return changed;
|
||||
// }
|
||||
//
|
||||
// function moveUp() {
|
||||
// let changed = false;
|
||||
// for (let i = 0; i < gridSize; i++) {
|
||||
// const col = [board[i], board[i + gridSize], board[i + gridSize * 2], board[i + gridSize * 3]];
|
||||
// const newCol = moveRow(col);
|
||||
// if (JSON.stringify(col) !== JSON.stringify(newCol)) changed = true;
|
||||
// for (let j = 0; j < gridSize; j++) {
|
||||
// board[i + j * gridSize] = newCol[j];
|
||||
// }
|
||||
// }
|
||||
// return changed;
|
||||
// }
|
||||
//
|
||||
// function moveDown() {
|
||||
// let changed = false;
|
||||
// for (let i = 0; i < gridSize; i++) {
|
||||
// const col = [board[i], board[i + gridSize], board[i + gridSize * 2], board[i + gridSize * 3]].reverse();
|
||||
// const newCol = moveRow(col).reverse();
|
||||
// if (JSON.stringify([board[i], board[i + gridSize], board[i + gridSize * 2], board[i + gridSize * 3]]) !== JSON.stringify(newCol)) changed = true;
|
||||
// for (let j = 0; j < gridSize; j++) {
|
||||
// board[i + j * gridSize] = newCol[j];
|
||||
// }
|
||||
// }
|
||||
// return changed;
|
||||
// }
|
||||
//
|
||||
// // ----- 게임 상태 관리 -----
|
||||
// function isGameOver() {
|
||||
// if (!board.includes(0)) {
|
||||
// for (let i = 0; i < gridSize; i++) {
|
||||
// for (let j = 0; j < gridSize; j++) {
|
||||
// const current = board[i * gridSize + j];
|
||||
// if ((j < gridSize - 1 && current === board[i * gridSize + j + 1]) ||
|
||||
// (i < gridSize - 1 && current === board[(i + 1) * gridSize + j])) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// function handleMove(moveFunction) {
|
||||
// if (moveFunction()) {
|
||||
// addNumber();
|
||||
// updateBoard();
|
||||
// if (isGameOver()) {
|
||||
// finalScoreDisplay.textContent = score;
|
||||
// gameOverPopup.style.display = 'flex';
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // ----- 이벤트 리스너 -----
|
||||
// document.addEventListener('keydown', (e) => {
|
||||
// switch (e.key) {
|
||||
// case 'ArrowUp': handleMove(moveUp); break;
|
||||
// case 'ArrowDown': handleMove(moveDown); break;
|
||||
// case 'ArrowLeft': handleMove(moveLeft); break;
|
||||
// case 'ArrowRight': handleMove(moveRight); break;
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// gameBoard.addEventListener('touchstart', (e) => {
|
||||
// touchStartX = e.changedTouches[0].screenX;
|
||||
// touchStartY = e.changedTouches[0].screenY;
|
||||
// });
|
||||
//
|
||||
// gameBoard.addEventListener('touchmove', (e) => e.preventDefault(), { passive: false });
|
||||
//
|
||||
// gameBoard.addEventListener('touchend', (e) => {
|
||||
// touchEndX = e.changedTouches[0].screenX;
|
||||
// touchEndY = e.changedTouches[0].screenY;
|
||||
// handleSwipe();
|
||||
// });
|
||||
//
|
||||
// function handleSwipe() {
|
||||
// const deltaX = touchEndX - touchStartX;
|
||||
// const deltaY = touchEndY - touchStartY;
|
||||
// const swipeThreshold = 30;
|
||||
//
|
||||
// if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
||||
// if (Math.abs(deltaX) > swipeThreshold) {
|
||||
// handleMove(deltaX > 0 ? moveRight : moveLeft);
|
||||
// }
|
||||
// } else {
|
||||
// if (Math.abs(deltaY) > swipeThreshold) {
|
||||
// handleMove(deltaY > 0 ? moveDown : moveUp);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // ----- 랭킹 API 연동 -----
|
||||
// saveScoreButton.addEventListener('click', async () => {
|
||||
// const playerName = playerNameInput.value.trim();
|
||||
// if (playerName === "") return alert("이름을 입력해주세요.");
|
||||
//
|
||||
// try {
|
||||
// // (★ 수정) user.js의 공통 submitRank 함수 호출
|
||||
// // 2048의 주 점수(primaryScore)는 score, 보조 점수(secondaryScore)는 없음.
|
||||
// await submitRank(currentGameType, currentContextId, playerName, score, null);
|
||||
//
|
||||
// gameOverPopup.style.display = 'none';
|
||||
// playerNameInput.value = '';
|
||||
// score = 0;
|
||||
// updateRankingList(); // 랭킹 리스트 새로고침
|
||||
// initializeBoard(); // 새 게임 시작
|
||||
//
|
||||
// } catch (error) {
|
||||
// console.error('Error submitting rank:', error);
|
||||
// alert('랭킹 등록 중 오류가 발생했습니다: ' + error.message);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// /**
|
||||
// * (★ 수정) user.js의 공통 fetchRanks 함수를 사용하도록 수정
|
||||
// */
|
||||
// async function updateRankingList() {
|
||||
// rankingList.innerHTML = '<li>로딩 중...</li>';
|
||||
// try {
|
||||
// // (★ 수정) user.js의 공통 fetchRanks 함수 호출
|
||||
// const rankings = await fetchRanks(currentGameType, currentContextId);
|
||||
//
|
||||
// rankingList.innerHTML = ''; // 리스트 비우기
|
||||
// if (!rankings || rankings.length === 0) {
|
||||
// rankingList.innerHTML = '<li>등록된 랭킹이 없습니다.</li>';
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// rankings.forEach((rank, index) => {
|
||||
// const li = document.createElement('li');
|
||||
// // (★ 수정) 공통 모델(GameRank)의 필드명(playerName, primaryScore)을 사용
|
||||
// li.innerHTML = `<span>${index + 1}. ${rank.playerName}</span><strong>${rank.primaryScore}점</strong>`;
|
||||
// rankingList.appendChild(li);
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.error('Error fetching ranks:', error);
|
||||
// rankingList.innerHTML = '<li>랭킹을 불러올 수 없습니다.</li>';
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // ----- 게임 시작 -----
|
||||
// updateRankingList();
|
||||
// initializeBoard();
|
||||
// });
|
||||
@ -1,53 +1,53 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const generateBtn = document.getElementById('generate-btn');
|
||||
const boardElement = document.getElementById('sudoku-board');
|
||||
const statusMessage = document.getElementById('status-message');
|
||||
|
||||
// 스도쿠 보드를 화면에 그리는 함수
|
||||
function renderBoard(puzzleString) {
|
||||
boardElement.innerHTML = ''; // 기존 보드 초기화
|
||||
if (!puzzleString || puzzleString.length !== 81) {
|
||||
statusMessage.textContent = '잘못된 퍼즐 데이터입니다.';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const char of puzzleString) {
|
||||
const cell = document.createElement('div');
|
||||
cell.classList.add('cell');
|
||||
cell.textContent = char;
|
||||
boardElement.appendChild(cell);
|
||||
}
|
||||
}
|
||||
|
||||
// 생성 버튼 클릭 이벤트 리스너
|
||||
generateBtn.addEventListener('click', async () => {
|
||||
// 버튼 비활성화 및 상태 메시지 업데이트
|
||||
generateBtn.disabled = true;
|
||||
statusMessage.textContent = '새로운 스도쿠 퍼즐을 생성 중입니다... 🧠';
|
||||
boardElement.innerHTML = ''; // 보드 비우기
|
||||
|
||||
try {
|
||||
// 백엔드 API 호출 (POST 요청)
|
||||
const response = await fetch('/sudoku/generate', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`서버 오류: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// 성공 시 보드 렌더링
|
||||
renderBoard(result.puzzle);
|
||||
statusMessage.textContent = `✅ 퍼즐 생성 완료! (ID: ${result.puzzleKey})`;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error generating Sudoku:', error);
|
||||
statusMessage.textContent = `❌ 생성 실패: ${error.message}`;
|
||||
} finally {
|
||||
// 성공/실패 여부와 관계없이 버튼 다시 활성화
|
||||
generateBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
// document.addEventListener('DOMContentLoaded', () => {
|
||||
// const generateBtn = document.getElementById('generate-btn');
|
||||
// const boardElement = document.getElementById('sudoku-board');
|
||||
// const statusMessage = document.getElementById('status-message');
|
||||
//
|
||||
// // 스도쿠 보드를 화면에 그리는 함수
|
||||
// function renderBoard(puzzleString) {
|
||||
// boardElement.innerHTML = ''; // 기존 보드 초기화
|
||||
// if (!puzzleString || puzzleString.length !== 81) {
|
||||
// statusMessage.textContent = '잘못된 퍼즐 데이터입니다.';
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// for (const char of puzzleString) {
|
||||
// const cell = document.createElement('div');
|
||||
// cell.classList.add('cell');
|
||||
// cell.textContent = char;
|
||||
// boardElement.appendChild(cell);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 생성 버튼 클릭 이벤트 리스너
|
||||
// generateBtn.addEventListener('click', async () => {
|
||||
// // 버튼 비활성화 및 상태 메시지 업데이트
|
||||
// generateBtn.disabled = true;
|
||||
// statusMessage.textContent = '새로운 스도쿠 퍼즐을 생성 중입니다... 🧠';
|
||||
// boardElement.innerHTML = ''; // 보드 비우기
|
||||
//
|
||||
// try {
|
||||
// // 백엔드 API 호출 (POST 요청)
|
||||
// const response = await fetch('/sudoku/generate', {
|
||||
// method: 'POST',
|
||||
// });
|
||||
//
|
||||
// if (!response.ok) {
|
||||
// throw new Error(`서버 오류: ${response.statusText}`);
|
||||
// }
|
||||
//
|
||||
// const result = await response.json();
|
||||
//
|
||||
// // 성공 시 보드 렌더링
|
||||
// renderBoard(result.puzzle);
|
||||
// statusMessage.textContent = `✅ 퍼즐 생성 완료! (ID: ${result.puzzleKey})`;
|
||||
//
|
||||
// } catch (error) {
|
||||
// console.error('Error generating Sudoku:', error);
|
||||
// statusMessage.textContent = `❌ 생성 실패: ${error.message}`;
|
||||
// } finally {
|
||||
// // 성공/실패 여부와 관계없이 버튼 다시 활성화
|
||||
// generateBtn.disabled = false;
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
@ -541,7 +541,7 @@ function save() {
|
||||
dataToSend.content = encodeURIComponent(JSON.stringify(quill.getContents())); // Quill 콘텐츠는 JSON 문자열로 변환 후 인코딩
|
||||
dataToSend.category = encodeURIComponent(dataToSend.category || 'none');
|
||||
dataToSend.tags = encodeURIComponent(dataToSend.tags || '');
|
||||
|
||||
dataToSend.posting = document.getElementById('post-published-switch').checked
|
||||
// 3. 현재 위치 좌표를 '수정 좌표'로 업데이트
|
||||
dataToSend.modifyLat = currentLat;
|
||||
dataToSend.modifyLon = currentLon;
|
||||
|
||||
Binary file not shown.
@ -1,694 +0,0 @@
|
||||
// /**
|
||||
// * ==============================================
|
||||
// * nonogram.js (게임 플레이 로직)
|
||||
// * (★ 리팩토링: 타이머 및 랭킹 등록 기능 추가)
|
||||
// * ==============================================
|
||||
// */
|
||||
// document.addEventListener('DOMContentLoaded', () => {
|
||||
// // 백엔드(Thymeleaf)로부터 puzzleData가 제대로 전달되었는지 확인
|
||||
// // 이 변수는 nonogram.html에만 존재하므로, upload.html에서는 이 로직이 실행되지 않음.
|
||||
// if (typeof puzzleData === 'undefined' || !puzzleData) {
|
||||
// // game-board가 없는 upload.html에서는 오류를 뱉지 않고 조용히 종료됨.
|
||||
// const gb = document.getElementById('game-board');
|
||||
// if (gb) {
|
||||
// gb.innerHTML = '<h2>오류: 퍼즐 데이터를 불러올 수 없습니다.</h2>';
|
||||
// }
|
||||
// return; // upload.html에서는 여기서 즉시 return됨.
|
||||
// }
|
||||
//
|
||||
// // --- DOM 요소 참조 (게임 페이지 전용) ---
|
||||
// const modeSelector = document.getElementById('mode-selector');
|
||||
// const gameBoard = document.getElementById('game-board');
|
||||
// const pointsDisplay = document.getElementById('points-display');
|
||||
// const hintBtn = document.getElementById('hint-btn');
|
||||
// const resultOverlay = document.getElementById('result-overlay');
|
||||
// const modalTitle = document.getElementById('modal-title');
|
||||
// const modalMessage = document.getElementById('modal-message');
|
||||
// const modalButtons = document.getElementById('modal-buttons');
|
||||
//
|
||||
//
|
||||
// // --- (★ 수정) 게임 상태 변수 (타이머 추가) ---
|
||||
// let currentMode = 'fill';
|
||||
// let points = 5;
|
||||
// let isGameFinished = false;
|
||||
// let gameStartTime = 0; // (★ 신규) 게임 시작 시간 (ms)
|
||||
//
|
||||
// let isDragging = false;
|
||||
// let dragAction = null;
|
||||
// let startCell = null;
|
||||
// let lastHoveredCell = null;
|
||||
// let currentSelection = new Set();
|
||||
// let affectedRows = new Set();
|
||||
// let affectedCols = new Set();
|
||||
//
|
||||
// // --- 퍼즐 데이터 및 플레이어 진행 상황 ---
|
||||
// const solution = puzzleData.solutionGrid;
|
||||
// const numRows = solution.length;
|
||||
// const numCols = solution[0].length;
|
||||
// let playerGrid = Array(numRows).fill(0).map(() => Array(numCols).fill(0));
|
||||
// let lockedRows = Array(numRows).fill(false);
|
||||
// let lockedCols = Array(numCols).fill(false);
|
||||
//
|
||||
//
|
||||
// function updateMode() {
|
||||
// currentMode = document.querySelector('input[name="play-mode"]:checked').value;
|
||||
// }
|
||||
//
|
||||
// function calculateCellSize() {
|
||||
// // ... (셀 크기 계산 로직 - 수정 없음) ...
|
||||
// const tempContainer = document.createElement('div');
|
||||
// tempContainer.style.position = 'absolute';
|
||||
// tempContainer.style.visibility = 'hidden';
|
||||
// const tempCell = document.createElement('div');
|
||||
// tempCell.className = 'clue-cell';
|
||||
// tempCell.textContent = '0';
|
||||
// tempContainer.appendChild(tempCell);
|
||||
// document.body.appendChild(tempContainer);
|
||||
// const fontHeight = tempCell.offsetHeight;
|
||||
// tempCell.textContent = '10';
|
||||
// const doubleDigitWidth = tempCell.offsetWidth;
|
||||
// document.body.removeChild(tempContainer);
|
||||
// const baseSize = Math.max(fontHeight, doubleDigitWidth, 30);
|
||||
// return baseSize + 10;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * (★ 수정) drawBoard (타이머 시작점 추가)
|
||||
// */
|
||||
// function drawBoard(cellSize) {
|
||||
// // ... (모든 보드 그리기 DOM 생성 로직 - 수정 없음) ...
|
||||
// gameBoard.style.gridTemplateColumns = `${cellSize * 2}px 1fr`;
|
||||
// gameBoard.style.gridTemplateRows = `${cellSize * 2}px 1fr`;
|
||||
// const corner = document.createElement('div');
|
||||
// const colCluesContainer = document.createElement('div');
|
||||
// colCluesContainer.className = 'col-clues-container';
|
||||
// const rowCluesContainer = document.createElement('div');
|
||||
// rowCluesContainer.className = 'row-clues-container';
|
||||
// const puzzleGridContainer = document.createElement('div');
|
||||
// puzzleGridContainer.className = 'puzzle-grid-container';
|
||||
// puzzleGridContainer.style.gridTemplateColumns = `repeat(${numCols}, ${cellSize}px)`;
|
||||
// puzzleGridContainer.style.gridTemplateRows = `repeat(${numRows}, ${cellSize}px)`;
|
||||
//
|
||||
// puzzleData.colClues.forEach((clues, index) => {
|
||||
// const clueCell = document.createElement('div');
|
||||
// clueCell.className = 'clue-cell col-clue';
|
||||
// clueCell.id = `col-clue-${index}`;
|
||||
// clueCell.style.width = `${cellSize}px`;
|
||||
// clueCell.innerHTML = clues.join('<br>');
|
||||
// if ((index + 1) % 5 === 0 && index < numCols - 1) clueCell.classList.add('guide-line-right');
|
||||
// colCluesContainer.appendChild(clueCell);
|
||||
// });
|
||||
// puzzleData.rowClues.forEach((clues, index) => {
|
||||
// const clueCell = document.createElement('div');
|
||||
// clueCell.className = 'clue-cell row-clue';
|
||||
// clueCell.id = `row-clue-${index}`;
|
||||
// clueCell.style.height = `${cellSize}px`;
|
||||
// clueCell.textContent = clues.join(' ');
|
||||
// if ((index + 1) % 5 === 0 && index < numRows - 1) clueCell.classList.add('guide-line-bottom');
|
||||
// rowCluesContainer.appendChild(clueCell);
|
||||
// });
|
||||
// for (let r = 0; r < numRows; r++) {
|
||||
// for (let c = 0; c < numCols; c++) {
|
||||
// const cell = document.createElement('div');
|
||||
// cell.className = 'grid-cell';
|
||||
// cell.dataset.row = r;
|
||||
// cell.dataset.col = c;
|
||||
// if ((c + 1) % 5 === 0 && c < numCols - 1) cell.classList.add('guide-line-right');
|
||||
// if ((r + 1) % 5 === 0 && r < numRows - 1) cell.classList.add('guide-line-bottom');
|
||||
// puzzleGridContainer.appendChild(cell);
|
||||
// }
|
||||
// }
|
||||
// gameBoard.appendChild(corner);
|
||||
// gameBoard.appendChild(colCluesContainer);
|
||||
// gameBoard.appendChild(rowCluesContainer);
|
||||
// gameBoard.appendChild(puzzleGridContainer);
|
||||
//
|
||||
// // (★ 신규) 보드가 그려지는 시점을 게임 시작 시간으로 기록
|
||||
// gameStartTime = Date.now();
|
||||
//
|
||||
// attachEventListeners(puzzleGridContainer);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * 화면 너비에 맞춰 게임 보드 전체를 비율 그대로 축소/확대합니다.
|
||||
// */
|
||||
// function fitBoardToScreen() {
|
||||
// const viewport = document.getElementById('board-viewport');
|
||||
// const board = document.getElementById('game-board');
|
||||
// board.style.transform = 'scale(1)';
|
||||
// const boardRect = board.getBoundingClientRect();
|
||||
// const viewportRect = viewport.getBoundingClientRect();
|
||||
// if (boardRect.width > viewportRect.width) {
|
||||
// const scale = viewportRect.width / boardRect.width;
|
||||
// board.style.transform = `scale(${scale})`;
|
||||
// viewport.style.height = `${boardRect.height * scale}px`;
|
||||
// } else {
|
||||
// board.style.transform = 'scale(1)';
|
||||
// viewport.style.height = `${boardRect.height}px`;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * 셀의 상태를 변경하는 유일한 함수. 모든 사용자 입력은 이 함수를 거칩니다.
|
||||
// */
|
||||
// function updateCellState(cell, action) {
|
||||
// if (isGameFinished) return;
|
||||
// const row = parseInt(cell.dataset.row);
|
||||
// const col = parseInt(cell.dataset.col);
|
||||
// if (lockedRows[row] || lockedCols[col]) return;
|
||||
// affectedRows.add(row);
|
||||
// affectedCols.add(col);
|
||||
// const currentState = playerGrid[row][col];
|
||||
// let newState = currentState;
|
||||
// if (action === 'fill') {
|
||||
// if (solution[row][col] === 0) {
|
||||
// points--;
|
||||
// updatePointsDisplay();
|
||||
// cell.classList.add('incorrect');
|
||||
// setTimeout(() => cell.classList.remove('incorrect'), 500);
|
||||
// if (points <= 0) triggerGameOver();
|
||||
// return;
|
||||
// }
|
||||
// newState = 1;
|
||||
// } else if (action === 'mark') {
|
||||
// newState = -1;
|
||||
// } else if (action === 'clear') {
|
||||
// newState = 0;
|
||||
// }
|
||||
// if (currentState !== newState) {
|
||||
// playerGrid[row][col] = newState;
|
||||
// cell.classList.toggle('filled', newState === 1);
|
||||
// cell.classList.toggle('marked', newState === -1);
|
||||
// }
|
||||
// }
|
||||
// // --- (이벤트 리스너 및 드래그/터치 핸들러) ---
|
||||
// // (★ 수정 없음) attachEventListeners, handleDragStart, handleDragMove, handleDragEnd
|
||||
// // (★ 수정 없음) updateSelectionVisuals, clearSelectionVisuals
|
||||
// // --- (모두 동일하게 유지) ---
|
||||
// function attachEventListeners(grid) {
|
||||
// grid.addEventListener('mousedown', (e) => handleDragStart(e));
|
||||
// grid.addEventListener('mouseover', (e) => handleDragMove(e));
|
||||
// grid.addEventListener('contextmenu', (e) => e.preventDefault());
|
||||
// grid.addEventListener('touchstart', (e) => handleDragStart(e), { passive: false });
|
||||
// grid.addEventListener('touchmove', (e) => handleDragMove(e), { passive: false });
|
||||
// }
|
||||
// window.addEventListener('mouseup', () => handleDragEnd());
|
||||
// window.addEventListener('touchend', () => handleDragEnd());
|
||||
// modeSelector.addEventListener('change', updateMode);
|
||||
// function handleDragStart(e) {
|
||||
// if (isGameFinished || !e.target.classList.contains('grid-cell')) return;
|
||||
// isDragging = true;
|
||||
// e.preventDefault();
|
||||
// const cell = e.target;
|
||||
// const currentState = playerGrid[parseInt(cell.dataset.row)][parseInt(cell.dataset.col)];
|
||||
// if (e.type === 'mousedown') {
|
||||
// if (e.button === 0) {
|
||||
// dragAction = (currentState === 1) ? 'clear' : 'fill';
|
||||
// document.querySelector('input[name="play-mode"][value="fill"]').checked = true;
|
||||
// } else if (e.button === 2) {
|
||||
// dragAction = (currentState === -1) ? 'clear' : 'mark';
|
||||
// document.querySelector('input[name="play-mode"][value="mark"]').checked = true;
|
||||
// }
|
||||
// } else {
|
||||
// const currentMode = document.querySelector('input[name="play-mode"]:checked').value;
|
||||
// if (currentMode === 'fill') {
|
||||
// dragAction = (currentState === 1) ? 'clear' : 'fill';
|
||||
// } else {
|
||||
// dragAction = (currentState === -1) ? 'clear' : 'mark';
|
||||
// }
|
||||
// }
|
||||
// startCell = { row: parseInt(cell.dataset.row), col: parseInt(cell.dataset.col) };
|
||||
// lastHoveredCell = startCell;
|
||||
// updateSelectionVisuals();
|
||||
// }
|
||||
// function handleDragMove(e) {
|
||||
// if (!isDragging) return;
|
||||
// e.preventDefault();
|
||||
// const target = (e.touches)
|
||||
// ? document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY)
|
||||
// : e.target;
|
||||
// if (target && target.classList.contains('grid-cell')) {
|
||||
// const row = parseInt(target.dataset.row);
|
||||
// const col = parseInt(target.dataset.col);
|
||||
// if (row !== lastHoveredCell.row || col !== lastHoveredCell.col) {
|
||||
// lastHoveredCell = { row, col };
|
||||
// updateSelectionVisuals();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// function handleDragEnd() {
|
||||
// if (!isDragging) return;
|
||||
// currentSelection.forEach(cell => updateCellState(cell, dragAction));
|
||||
// clearSelectionVisuals();
|
||||
// if (dragAction === 'fill' || dragAction === 'clear') {
|
||||
// checkAndLockCompletedLines(affectedRows, affectedCols);
|
||||
// }
|
||||
// checkWinCondition();
|
||||
// isDragging = false;
|
||||
// dragAction = null;
|
||||
// startCell = null;
|
||||
// lastHoveredCell = null;
|
||||
// currentSelection.clear();
|
||||
// affectedRows.clear();
|
||||
// affectedCols.clear();
|
||||
// }
|
||||
// /**
|
||||
// * 드래그 중인 사각형 영역을 계산하고 시각적으로 업데이트하는 함수
|
||||
// */
|
||||
// function updateSelectionVisuals() {
|
||||
// const newSelection = new Set();
|
||||
// if (!startCell || !lastHoveredCell) return;
|
||||
// const r1 = Math.min(startCell.row, lastHoveredCell.row);
|
||||
// const r2 = Math.max(startCell.row, lastHoveredCell.row);
|
||||
// const c1 = Math.min(startCell.col, lastHoveredCell.col);
|
||||
// const c2 = Math.max(startCell.col, lastHoveredCell.col);
|
||||
// for (let r = r1; r <= r2; r++) {
|
||||
// for (let c = c1; c <= c2; c++) {
|
||||
// const cell = document.querySelector(`.grid-cell[data-row='${r}'][data-col='${c}']`);
|
||||
// if (cell) newSelection.add(cell);
|
||||
// }
|
||||
// }
|
||||
// currentSelection.forEach(cell => {
|
||||
// if (!newSelection.has(cell)) cell.classList.remove('selecting');
|
||||
// });
|
||||
// newSelection.forEach(cell => {
|
||||
// if (!currentSelection.has(cell)) cell.classList.add('selecting');
|
||||
// });
|
||||
// currentSelection = newSelection;
|
||||
// }
|
||||
// /**
|
||||
// * 모든 시각적 피드백을 제거하는 함수
|
||||
// */
|
||||
// function clearSelectionVisuals() {
|
||||
// currentSelection.forEach(cell => cell.classList.remove('selecting'));
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 특정 행이 '칠해야 할 칸'을 모두 만족했는지 검사
|
||||
// */
|
||||
// function isRowComplete(rowIndex) {
|
||||
// for (let c = 0; c < numCols; c++) {
|
||||
// if (solution[rowIndex][c] === 1 && playerGrid[rowIndex][c] !== 1) return false;
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
// /**
|
||||
// * 특정 열이 '칠해야 할 칸'을 모두 만족했는지 검사
|
||||
// */
|
||||
// function isColComplete(colIndex) {
|
||||
// for (let r = 0; r < numRows; r++) {
|
||||
// if (solution[r][colIndex] === 1 && playerGrid[r][colIndex] !== 1) return false;
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// // --- (게임 완료 체크 로직) ---
|
||||
// /**
|
||||
// * 완성된 라인을 확인하고 잠금 처리 및 스타일 변경 (X 자동 완성 없음)
|
||||
// */
|
||||
// function checkAndLockCompletedLines(rowsToCheck, colsToCheck) {
|
||||
// rowsToCheck.forEach(r => {
|
||||
// if (!lockedRows[r] && isRowComplete(r)) {
|
||||
// lockedRows[r] = true;
|
||||
// document.getElementById(`row-clue-${r}`).classList.add('completed');
|
||||
// for (let c = 0; c < numCols; c++) {
|
||||
// document.querySelector(`.grid-cell[data-row='${r}'][data-col='${c}']`).classList.add('locked');
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// colsToCheck.forEach(c => {
|
||||
// if (!lockedCols[c] && isColComplete(c)) {
|
||||
// lockedCols[c] = true;
|
||||
// document.getElementById(`col-clue-${c}`).classList.add('completed');
|
||||
// for (let r = 0; r < numRows; r++) {
|
||||
// document.querySelector(`.grid-cell[data-row='${r}'][data-col='${c}']`).classList.add('locked');
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// function checkWinCondition() {
|
||||
// if (isGameFinished) return;
|
||||
// for (let r = 0; r < numRows; r++) {
|
||||
// for (let c = 0; c < numCols; c++) {
|
||||
// const playerState = (playerGrid[r][c] === 1) ? 1 : 0;
|
||||
// if (playerState !== solution[r][c]) return;
|
||||
// }
|
||||
// }
|
||||
// triggerGameSuccess();
|
||||
// }
|
||||
//
|
||||
//
|
||||
// function updatePointsDisplay() {
|
||||
// pointsDisplay.textContent = points;
|
||||
// hintBtn.disabled = (points <= 0 || isGameFinished);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * (★ 신규) 노노그램 랭킹 등록을 처리하는 함수
|
||||
// * 이 함수는 user.js에 정의된 공통 submitRank 함수를 호출합니다.
|
||||
// */
|
||||
// async function submitNonogramRank(completionTime, hintsUsed) {
|
||||
// const playerName = prompt("랭킹에 등록할 이름을 입력하세요:", "Player");
|
||||
// if (!playerName || playerName.trim() === "") return;
|
||||
//
|
||||
// try {
|
||||
// // (★ 신규) user.js의 공통 submitRank 함수 호출
|
||||
// // 주 점수(primaryScore) = 완료 시간(초) (낮을수록 좋음)
|
||||
// // 보조 점수(secondaryScore) = 사용한 힌트 수(5-남은포인트) (낮을수록 좋음)
|
||||
// await submitRank(
|
||||
// 'NONOGRAM', // GameType
|
||||
// puzzleData.id, // ContextId (퍼즐 고유 ID)
|
||||
// playerName.trim(), // playerName
|
||||
// completionTime, // primaryScore (시간)
|
||||
// hintsUsed // secondaryScore (힌트 사용 횟수)
|
||||
// );
|
||||
//
|
||||
// alert("랭킹이 등록되었습니다!");
|
||||
// // 랭킹 등록 버튼 비활성화 (중복 제출 방지)
|
||||
// const submitBtn = document.getElementById('modal-submit-rank-btn');
|
||||
// if (submitBtn) submitBtn.disabled = true;
|
||||
//
|
||||
// } catch (error) {
|
||||
// console.error("Rank submission failed:", error);
|
||||
// alert("랭킹 등록에 실패했습니다: " + error.message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * (★ 수정) 성공/실패 모달 (랭킹 등록 버튼 추가를 위해 ID 할당 기능 추가)
|
||||
// */
|
||||
// function showResultModal(config) {
|
||||
// modalTitle.textContent = config.title;
|
||||
// modalMessage.textContent = config.message;
|
||||
// modalButtons.innerHTML = '';
|
||||
// config.buttons.forEach(btnInfo => {
|
||||
// const button = document.createElement('button');
|
||||
// button.textContent = btnInfo.text;
|
||||
// button.className = btnInfo.class || '';
|
||||
// button.onclick = btnInfo.action;
|
||||
// if (btnInfo.id) button.id = btnInfo.id; // (★ 신규) 버튼 ID 할당 기능
|
||||
// modalButtons.appendChild(button);
|
||||
// });
|
||||
// resultOverlay.classList.remove('hidden');
|
||||
// setTimeout(() => resultOverlay.classList.add('visible'), 10);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * 게임 실패 처리
|
||||
// */
|
||||
// function triggerGameOver() {
|
||||
// if (isGameFinished) return;
|
||||
// isGameFinished = true;
|
||||
// document.querySelector('.puzzle-grid-container').style.pointerEvents = 'none';
|
||||
// hintBtn.disabled = true;
|
||||
// showResultModal({
|
||||
// title: 'Failure', message: '포인트를 모두 사용했습니다.', buttons: [
|
||||
// { text: '재시도 (Retry)', class: 'primary', action: () => window.location.reload() },
|
||||
// { text: '홈으로 (Home)', action: () => window.location.href = '/' }
|
||||
// ]
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * (★ 수정) 게임 성공 처리 (타이머 계산 및 랭킹 버튼 추가)
|
||||
// */
|
||||
// function triggerGameSuccess() {
|
||||
// if (isGameFinished) return;
|
||||
// isGameFinished = true;
|
||||
//
|
||||
// // (★ 신규) 게임 완료 시간 및 힌트 사용량 계산
|
||||
// const completionTimeSeconds = Math.floor((Date.now() - gameStartTime) / 1000);
|
||||
// const hintsUsed = 5 - points; // 힌트 사용량 = 5 - 남은 포인트
|
||||
//
|
||||
// // --- 요소 참조 및 상호작용 비활성화 ---
|
||||
// const viewport = document.getElementById('board-viewport');
|
||||
// const puzzleGridContainer = document.querySelector('.puzzle-grid-container');
|
||||
// const grayscaleImg = document.getElementById('grayscale-reveal');
|
||||
// const originalImg = document.getElementById('original-reveal');
|
||||
// puzzleGridContainer.style.pointerEvents = 'none';
|
||||
// hintBtn.disabled = true;
|
||||
//
|
||||
// // --- 애니메이션 위치 및 크기 계산 ---
|
||||
// const gridRect = puzzleGridContainer.getBoundingClientRect();
|
||||
// const viewportRect = viewport.getBoundingClientRect();
|
||||
// const top = gridRect.top - viewportRect.top;
|
||||
// const left = gridRect.left - viewportRect.top; // (오타 수정) viewportRect.top -> viewportRect.left
|
||||
//
|
||||
// [grayscaleImg, originalImg].forEach(img => {
|
||||
// img.style.top = `${top}px`;
|
||||
// img.style.left = `${left}px`;
|
||||
// img.style.width = `${gridRect.width}px`;
|
||||
// img.style.height = `${gridRect.height}px`;
|
||||
// img.src = (img.id === 'grayscale-reveal') ? puzzleData.grayscaleImage : puzzleData.originalImage;
|
||||
// });
|
||||
//
|
||||
// // --- 애니메이션 순차 실행 ---
|
||||
// setTimeout(() => {
|
||||
// grayscaleImg.style.opacity = '1';
|
||||
// setTimeout(() => {
|
||||
// originalImg.style.opacity = '1';
|
||||
// setTimeout(() => {
|
||||
// // (★ 수정) 모달 버튼 설정에 "랭킹 등록" 버튼 추가
|
||||
// showResultModal({
|
||||
// title: 'Success! 🎉',
|
||||
// message: `퍼즐을 완성했습니다! (시간: ${completionTimeSeconds}초, 힌트 사용: ${hintsUsed}개)`,
|
||||
// buttons: [
|
||||
// {
|
||||
// text: '랭킹 등록',
|
||||
// class: 'primary',
|
||||
// id: 'modal-submit-rank-btn', // (★ 신규) 랭킹 제출 버튼
|
||||
// action: () => submitNonogramRank(completionTimeSeconds, hintsUsed)
|
||||
// },
|
||||
// { text: '다른 문제 풀기', action: () => window.location.href = '/puzzle/play' },
|
||||
// { text: '홈으로', action: () => window.location.href = '/' }
|
||||
// ]
|
||||
// });
|
||||
// }, 2000);
|
||||
// }, 2000);
|
||||
// }, 500);
|
||||
// }
|
||||
//
|
||||
// // 힌트 버튼 클릭 이벤트 처리
|
||||
// hintBtn.addEventListener('click', () => {
|
||||
// if (points <= 0 || isGameFinished) return;
|
||||
// points--;
|
||||
// updatePointsDisplay();
|
||||
// const hintCandidates = [];
|
||||
// for (let r = 0; r < numRows; r++) {
|
||||
// for (let c = 0; c < numCols; c++) {
|
||||
// if (solution[r][c] === 1 && playerGrid[r][c] !== 1) {
|
||||
// hintCandidates.push({ r, c });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (hintCandidates.length > 0) {
|
||||
// const hint = hintCandidates[Math.floor(Math.random() * hintCandidates.length)];
|
||||
// const cellToReveal = document.querySelector(`.grid-cell[data-row='${hint.r}'][data-col='${hint.c}']`);
|
||||
//
|
||||
// updateCellState(cellToReveal, 'fill');
|
||||
//
|
||||
// const hintAffectedRows = new Set([hint.r]);
|
||||
// const hintAffectedCols = new Set([hint.c]);
|
||||
// checkAndLockCompletedLines(hintAffectedRows, hintAffectedCols);
|
||||
// checkWinCondition();
|
||||
// } else {
|
||||
// alert("더 이상 사용할 힌트가 없습니다!");
|
||||
// points++;
|
||||
// updatePointsDisplay();
|
||||
// }
|
||||
// if (points <= 0 && !isGameFinished) {
|
||||
// triggerGameOver();
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// // --- 초기 실행 ---
|
||||
// const optimalCellSize = calculateCellSize();
|
||||
// drawBoard(optimalCellSize);
|
||||
// updatePointsDisplay();
|
||||
// updateMode();
|
||||
//
|
||||
// requestAnimationFrame(() => {
|
||||
// fitBoardToScreen();
|
||||
// window.addEventListener('resize', fitBoardToScreen);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * ==============================================
|
||||
// * upload.js (업로드 페이지 로직)
|
||||
// * (★ 리팩토링: 통합 API 경로 사용)
|
||||
// * ==============================================
|
||||
// */
|
||||
// let currentPuzzleData = null; // 업로드 성공 시 퍼즐 데이터 저장
|
||||
//
|
||||
// // (★ 수정 없음) 업로드 페이지용 성공 애니메이션 함수
|
||||
// function showSuccessAnimation() {
|
||||
// if (!currentPuzzleData) return;
|
||||
//
|
||||
// const puzzleContainer = document.getElementById('puzzle-container');
|
||||
// const grayscaleImg = document.getElementById('grayscale-reveal');
|
||||
// const originalImg = document.getElementById('original-reveal');
|
||||
//
|
||||
// grayscaleImg.src = currentPuzzleData.grayscaleImage;
|
||||
// originalImg.src = currentPuzzleData.originalImage;
|
||||
//
|
||||
// puzzleContainer.style.transition = 'opacity 0.5s';
|
||||
// puzzleContainer.style.opacity = '0';
|
||||
// grayscaleImg.style.opacity = '1';
|
||||
//
|
||||
// setTimeout(() => {
|
||||
// grayscaleImg.style.opacity = '0';
|
||||
// originalImg.style.opacity = '1';
|
||||
// }, 2000);
|
||||
// }
|
||||
//
|
||||
// // (★ 수정 없음) 업로드 페이지용 퍼즐 미리보기 그리기 함수
|
||||
// function drawPuzzle(puzzleData) {
|
||||
// const container = document.getElementById('puzzle-container');
|
||||
// container.innerHTML = '';
|
||||
//
|
||||
// const { solutionGrid, rowClues, colClues } = puzzleData;
|
||||
// const numRows = solutionGrid.length;
|
||||
// const numCols = solutionGrid[0].length;
|
||||
//
|
||||
// container.style.gridTemplateColumns = `auto repeat(${numCols}, 1fr)`;
|
||||
// container.style.gridTemplateRows = `auto repeat(${numRows}, 1fr)`;
|
||||
//
|
||||
// // 1. 코너
|
||||
// const corner = document.createElement('div');
|
||||
// corner.className = 'grid-cell';
|
||||
// container.appendChild(corner);
|
||||
//
|
||||
// // 2. 열 힌트
|
||||
// for (const clues of colClues) {
|
||||
// const clueCell = document.createElement('div');
|
||||
// clueCell.className = 'clue-cell';
|
||||
// clueCell.innerHTML = clues.join('<br>');
|
||||
// container.appendChild(clueCell);
|
||||
// }
|
||||
//
|
||||
// // 3. 행 힌트 및 정답 그리드
|
||||
// for (let i = 0; i < numRows; i++) {
|
||||
// const rowClueCell = document.createElement('div');
|
||||
// rowClueCell.className = 'clue-cell';
|
||||
// rowClueCell.textContent = rowClues[i].join(' ');
|
||||
// container.appendChild(rowClueCell);
|
||||
//
|
||||
// for (let j = 0; j < numCols; j++) {
|
||||
// const cell = document.createElement('div');
|
||||
// cell.className = 'solution-cell';
|
||||
// if (solutionGrid[i][j] === 1) {
|
||||
// cell.classList.add('filled');
|
||||
// } else {
|
||||
// cell.classList.add('empty');
|
||||
// }
|
||||
// container.appendChild(cell);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // upload.js의 DOMContentLoaded 리스너
|
||||
// document.addEventListener('DOMContentLoaded', () => {
|
||||
//
|
||||
// const createBtn = document.getElementById('createBtn');
|
||||
//
|
||||
// // createBtn이 없는 nonogram.html(게임 페이지)에서는 이 리스너가 아무것도 실행하지 않음.
|
||||
// if (!createBtn) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // (업로드 페이지 전용 로직)
|
||||
// createBtn.addEventListener('click', async () => {
|
||||
// const uploader = document.getElementById('imageUploader');
|
||||
// const statusDiv = document.getElementById('status');
|
||||
// const puzzleContainer = document.getElementById('puzzle-container');
|
||||
// const testSuccessBtn = document.getElementById('test-success-btn');
|
||||
// const deleteBtn = document.getElementById('delete-btn');
|
||||
// const playBtn = document.getElementById('play-btn');
|
||||
//
|
||||
// if (uploader.files.length === 0) {
|
||||
// statusDiv.textContent = '이미지 파일을 선택해주세요.';
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// const imageFile = uploader.files[0];
|
||||
// const formData = new FormData();
|
||||
// formData.append('imageFile', imageFile);
|
||||
//
|
||||
// statusDiv.textContent = '문제를 생성하는 중...';
|
||||
// puzzleContainer.innerHTML = '';
|
||||
//
|
||||
// try {
|
||||
// // (★ 수정) API 경로 변경 -> 통합 컨트롤러의 /puzzle/upload.bjx 호출
|
||||
// const response = await fetch('/puzzle/upload.bjx', {
|
||||
// method: 'POST',
|
||||
// body: formData,
|
||||
// });
|
||||
//
|
||||
// if (response.ok) {
|
||||
// const puzzleData = await response.json();
|
||||
// statusDiv.textContent = '문제 생성 성공!';
|
||||
// drawPuzzle(puzzleData); // 미리보기 그리기
|
||||
//
|
||||
// currentPuzzleData = puzzleData;
|
||||
// testSuccessBtn.addEventListener('click', showSuccessAnimation);
|
||||
//
|
||||
// testSuccessBtn.style.display = 'inline-block';
|
||||
// deleteBtn.style.display = 'inline-block';
|
||||
// playBtn.style.display = 'inline-block';
|
||||
// } else {
|
||||
// const errorMessage = await response.text();
|
||||
// statusDiv.textContent = `생성 실패: ${errorMessage}`;
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error('네트워크 오류:', error);
|
||||
// statusDiv.textContent = '서버와 통신 중 오류가 발생했습니다.';
|
||||
// }
|
||||
//
|
||||
// deleteBtn.addEventListener('click', async () => {
|
||||
// if (!currentPuzzleData || !currentPuzzleData.id) {
|
||||
// alert('삭제할 퍼즐이 선택되지 않았습니다.');
|
||||
// return;
|
||||
// }
|
||||
// if (!confirm('정말로 이 퍼즐을 삭제하시겠습니까?')) {
|
||||
// return;
|
||||
// }
|
||||
// try {
|
||||
// // (★ 수정) API 경로 변경 -> 통합 컨트롤러의 /puzzle/{id}.bjx 호출
|
||||
// const response = await fetch(`/puzzle/${currentPuzzleData.id}.bjx`, {
|
||||
// method: 'DELETE',
|
||||
// });
|
||||
//
|
||||
// if (response.ok) {
|
||||
// statusDiv.textContent = '퍼즐이 성공적으로 삭제되었습니다.';
|
||||
// puzzleContainer.innerHTML = '';
|
||||
// // (버그 수정) success-animation-container 내부의 img src를 초기화해야 함
|
||||
// document.getElementById('grayscale-reveal').src = "";
|
||||
// document.getElementById('original-reveal').src = "";
|
||||
//
|
||||
// testSuccessBtn.style.display = 'none';
|
||||
// deleteBtn.style.display = 'none';
|
||||
// playBtn.style.display = 'none';
|
||||
// currentPuzzleData = null;
|
||||
// } else {
|
||||
// statusDiv.textContent = `삭제 실패: 서버 오류 (${response.status})`;
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error('삭제 중 네트워크 오류:', error);
|
||||
// statusDiv.textContent = '삭제 중 오류가 발생했습니다.';
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// playBtn.addEventListener('click', () => {
|
||||
// if (currentPuzzleData && currentPuzzleData.id) {
|
||||
// // (★ 수정 없음) 이 경로는 PuzzleController의 페이지 서빙 경로와 일치하므로 올바름.
|
||||
// window.location.href = `/puzzle/play/${currentPuzzleData.id}`;
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,363 +0,0 @@
|
||||
// document.addEventListener('DOMContentLoaded', () => {
|
||||
// // DOM 요소
|
||||
// const setupContainer = document.getElementById('setup-container');
|
||||
// const gameContainer = document.getElementById('game-container');
|
||||
// const startBtn = document.getElementById('start-btn');
|
||||
// const boardElement = document.getElementById('sudoku-board');
|
||||
// const timerElement = document.getElementById('timer');
|
||||
// const scoreElement = document.getElementById('score');
|
||||
// const hintBtn = document.getElementById('hint-btn');
|
||||
// const undoBtn = document.getElementById('undo-btn');
|
||||
// const completeBtn = document.getElementById('complete-btn');
|
||||
// const numberInputButtons = document.getElementById('number-input-buttons');
|
||||
// const modalOverlay = document.getElementById('modal-overlay');
|
||||
// const gameOverModal = document.getElementById('game-over-modal');
|
||||
// const retryBtn = document.getElementById('retry-btn');
|
||||
// const submitRankBtn = document.getElementById('submit-rank-btn');
|
||||
// const rankingList = document.getElementById('ranking-list');
|
||||
// const closeModalBtn = document.getElementById('close-modal-btn');
|
||||
//
|
||||
// // 게임 상태 변수
|
||||
// let currentPuzzleId = null;
|
||||
// let solvedPuzzle = null;
|
||||
// let timerInterval = null;
|
||||
// let secondsElapsed = 0;
|
||||
// let selectedNumber = null;
|
||||
// let focusedCell = null;
|
||||
// let score = 5;
|
||||
// let history = [];
|
||||
//
|
||||
// // (★ 수정) API 호출 경로를 통합 컨트롤러(/puzzle) 경로로 변경
|
||||
// startBtn.addEventListener('click', async () => {
|
||||
// const difficulty = document.getElementById('difficulty-select').value;
|
||||
// try {
|
||||
// // (★ 수정) API 경로 변경: /sudoku/start -> /puzzle/sudoku/start
|
||||
// const response = await fetch(`/puzzle/sudoku/start?difficulty=${difficulty}`);
|
||||
// if (!response.ok) throw new Error('서버에서 게임 데이터를 가져오지 못했습니다.');
|
||||
// const gameData = await response.json();
|
||||
//
|
||||
// currentContextId = gameData.puzzleId;
|
||||
// solvedPuzzle = gameData.solution;
|
||||
//
|
||||
// history = [];
|
||||
// score = 5;
|
||||
// updateScoreDisplay();
|
||||
//
|
||||
// renderBoard(gameData.question);
|
||||
// startTimer();
|
||||
// updateButtonStates();
|
||||
//
|
||||
// setupContainer.classList.add('hidden');
|
||||
// gameContainer.classList.remove('hidden');
|
||||
// numberInputButtons.classList.remove('hidden');
|
||||
// gameOverModal.classList.add('hidden');
|
||||
// } catch (error) {
|
||||
// alert('게임 로딩에 실패했습니다: ' + error.message);
|
||||
// console.error(error);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// function renderBoard(puzzleString) {
|
||||
// boardElement.innerHTML = '';
|
||||
// for (let i = 0; i < 81; i++) {
|
||||
// const cell = document.createElement('div');
|
||||
// cell.classList.add('cell');
|
||||
// cell.dataset.index = i;
|
||||
//
|
||||
// if (puzzleString[i] !== '0') {
|
||||
// cell.textContent = puzzleString[i];
|
||||
// } else {
|
||||
// cell.classList.add('editable');
|
||||
// }
|
||||
// boardElement.appendChild(cell);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// function startTimer() {
|
||||
// secondsElapsed = 0;
|
||||
// timerElement.textContent = '00:00';
|
||||
// clearInterval(timerInterval);
|
||||
// timerInterval = setInterval(() => {
|
||||
// secondsElapsed++;
|
||||
// const minutes = Math.floor(secondsElapsed / 60).toString().padStart(2, '0');
|
||||
// const seconds = (secondsElapsed % 60).toString().padStart(2, '0');
|
||||
// timerElement.textContent = `${minutes}:${seconds}`;
|
||||
// }, 1000);
|
||||
// }
|
||||
//
|
||||
// function updateScoreDisplay() {
|
||||
// scoreElement.textContent = `SCORE: ${score}`;
|
||||
// if (score <= 0) {
|
||||
// clearInterval(timerInterval);
|
||||
// gameOverModal.classList.remove('hidden');
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// function updateButtonStates() {
|
||||
// const counts = {};
|
||||
// for (let i = 1; i <= 9; i++) counts[i] = 0;
|
||||
// boardElement.querySelectorAll('.cell').forEach(cell => {
|
||||
// const num = cell.textContent;
|
||||
// if (num && counts[num] !== undefined) counts[num]++;
|
||||
// });
|
||||
// for (let i = 1; i <= 9; i++) {
|
||||
// const btn = numberInputButtons.querySelector(`.num-btn[data-number="${i}"]`);
|
||||
// if (btn) {
|
||||
// if (counts[i] >= 9) {
|
||||
// btn.classList.add('completed');
|
||||
// if (selectedNumber == i) {
|
||||
// selectedNumber = null;
|
||||
// btn.classList.remove('selected');
|
||||
// }
|
||||
// } else {
|
||||
// btn.classList.remove('completed');
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // --- 게임 플레이 이벤트 핸들링 ---
|
||||
// numberInputButtons.addEventListener('click', (event) => {
|
||||
// const target = event.target.closest('button');
|
||||
// if (!target) return;
|
||||
//
|
||||
// if (target === undoBtn) {
|
||||
// undoAction();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (target.classList.contains('completed')) return;
|
||||
// document.querySelectorAll('.num-btn').forEach(btn => btn.classList.remove('selected'));
|
||||
//
|
||||
// if (target.classList.contains('num-btn')) {
|
||||
// const num = target.dataset.number;
|
||||
// selectedNumber = (selectedNumber === num) ? null : num;
|
||||
// if (selectedNumber) target.classList.add('selected');
|
||||
// }
|
||||
// highlightCells();
|
||||
// });
|
||||
//
|
||||
// boardElement.addEventListener('click', (event) => {
|
||||
// const targetCell = event.target.closest('.cell.editable');
|
||||
// if (!targetCell) {
|
||||
// if (focusedCell) focusedCell = null;
|
||||
// highlightCells();
|
||||
// return;
|
||||
// }
|
||||
// focusedCell = targetCell;
|
||||
//
|
||||
// if (selectedNumber) {
|
||||
// const previousValue = targetCell.textContent;
|
||||
// let newValue = (previousValue === selectedNumber) ? '' : selectedNumber;
|
||||
// targetCell.textContent = newValue;
|
||||
//
|
||||
// recordAction(targetCell, previousValue, newValue);
|
||||
// validateCell(targetCell);
|
||||
// updateButtonStates();
|
||||
// checkIfBoardIsFull();
|
||||
// }
|
||||
//
|
||||
// highlightCells();
|
||||
// });
|
||||
//
|
||||
// hintBtn.addEventListener('click', () => {
|
||||
// if (score <= 0) return;
|
||||
// const emptyCells = Array.from(boardElement.querySelectorAll('.cell.editable')).filter(cell => !cell.textContent);
|
||||
// if (emptyCells.length === 0) {
|
||||
// alert('모든 칸이 채워져 있습니다.');
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
|
||||
// const cellIndex = parseInt(randomCell.dataset.index);
|
||||
// const correctAnswer = solvedPuzzle[cellIndex];
|
||||
// const previousValue = randomCell.textContent;
|
||||
//
|
||||
// score--;
|
||||
// updateScoreDisplay();
|
||||
// recordAction(randomCell, previousValue, correctAnswer, true);
|
||||
//
|
||||
// randomCell.textContent = correctAnswer;
|
||||
// randomCell.classList.remove('editable', 'incorrect');
|
||||
//
|
||||
// updateButtonStates();
|
||||
// highlightCells();
|
||||
// checkIfBoardIsFull();
|
||||
// });
|
||||
//
|
||||
// function undoAction() {
|
||||
// if (history.length === 0) return;
|
||||
// const lastAction = history.pop();
|
||||
// const cell = boardElement.querySelector(`.cell[data-index="${lastAction.index}"]`);
|
||||
//
|
||||
// if (cell) {
|
||||
// cell.textContent = lastAction.previousValue;
|
||||
// if (lastAction.wasHint) {
|
||||
// cell.classList.add('editable');
|
||||
// }
|
||||
// validateCell(cell, false);
|
||||
// updateButtonStates();
|
||||
// highlightCells();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// function recordAction(cell, previousValue, newValue, wasHint = false) {
|
||||
// history.push({ index: cell.dataset.index, previousValue, newValue, wasHint });
|
||||
// }
|
||||
//
|
||||
// function validateCell(cell, deductPoint = true) {
|
||||
// if (!cell.textContent) {
|
||||
// cell.classList.remove('incorrect');
|
||||
// return;
|
||||
// }
|
||||
// const cellIndex = parseInt(cell.dataset.index);
|
||||
// const isCorrect = (cell.textContent === solvedPuzzle[cellIndex]);
|
||||
// if (!isCorrect) {
|
||||
// cell.classList.add('incorrect');
|
||||
// if (deductPoint && score > 0) {
|
||||
// score--;
|
||||
// updateScoreDisplay();
|
||||
// }
|
||||
// } else {
|
||||
// cell.classList.remove('incorrect');
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // --- 하이라이트 기능 ---
|
||||
// function highlightCells() {
|
||||
// document.querySelectorAll('.cell').forEach(cell => {
|
||||
// cell.classList.remove('highlight-focused', 'highlight-same-number', 'highlight-selected-number');
|
||||
// });
|
||||
// if (focusedCell) {
|
||||
// focusedCell.classList.add('highlight-focused');
|
||||
// const focusedValue = focusedCell.textContent;
|
||||
// if (focusedValue) {
|
||||
// document.querySelectorAll('.cell').forEach(cell => {
|
||||
// if (cell.textContent === focusedValue) cell.classList.add('highlight-same-number');
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// if (selectedNumber) {
|
||||
// document.querySelectorAll('.cell').forEach(cell => {
|
||||
// if (cell.textContent === selectedNumber) cell.classList.add('highlight-selected-number');
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // --- 게임 완료 및 모달 ---
|
||||
// async function checkSolution() {
|
||||
// let answerString = "";
|
||||
// boardElement.childNodes.forEach(cell => {
|
||||
// answerString += cell.textContent || '0';
|
||||
// });
|
||||
//
|
||||
// if (answerString.includes('0')) {
|
||||
// alert('모든 칸을 채워주세요!');
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// // (★ 수정) API 경로 변경: /sudoku/validate -> /puzzle/sudoku/validate
|
||||
// // (★ 수정) contextId(puzzleId) 변수 사용
|
||||
// const response = await fetch('/puzzle/sudoku/validate', {
|
||||
// method: 'POST',
|
||||
// headers: { 'Content-Type': 'application/json' },
|
||||
// body: JSON.stringify({ puzzleId: currentContextId, answer: answerString })
|
||||
// });
|
||||
// const result = await response.json();
|
||||
// if (result.correct) {
|
||||
// clearInterval(timerInterval);
|
||||
// alert('🎉 정답입니다!');
|
||||
// showRankingModal(); // 랭킹 모달 표시
|
||||
// } else {
|
||||
// alert('🤔 틀린 부분이 있습니다. 다시 확인해주세요.');
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error('정답 확인 중 오류 발생:', error);
|
||||
// alert('정답 확인 중 오류가 발생했습니다.');
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// function checkIfBoardIsFull() {
|
||||
// const emptyEditableCells = boardElement.querySelector('.cell.editable:empty');
|
||||
// if (!emptyEditableCells) {
|
||||
// checkSolution();
|
||||
// }
|
||||
// }
|
||||
// completeBtn.addEventListener('click', checkSolution);
|
||||
// /**
|
||||
// * (★ 수정) user.js의 공통 fetchRanks 함수를 사용하도록 수정
|
||||
// */
|
||||
// async function showRankingModal() {
|
||||
// modalOverlay.classList.remove('hidden');
|
||||
// document.getElementById('username-input').value = '';
|
||||
// submitRankBtn.disabled = false;
|
||||
// rankingList.innerHTML = '<li>로딩 중...</li>';
|
||||
//
|
||||
// try {
|
||||
// // (★ 수정) user.js의 공통 fetchRanks 함수 호출 (스도쿠 퍼즐 ID(ContextId) 전달)
|
||||
// const rankings = await fetchRanks(currentGameType, currentContextId);
|
||||
//
|
||||
// rankingList.innerHTML = '';
|
||||
// if (rankings.length === 0) {
|
||||
// rankingList.innerHTML = '<li>아직 등록된 랭킹이 없습니다.</li>';
|
||||
// } else {
|
||||
// rankings.forEach((rank, index) => {
|
||||
// const li = document.createElement('li');
|
||||
// // (★ 수정) 공통 모델 필드(primaryScore)를 시간(초)으로 변환
|
||||
// const minutes = Math.floor(rank.primaryScore / 60).toString().padStart(2, '0');
|
||||
// const seconds = (rank.primaryScore % 60).toString().padStart(2, '0');
|
||||
// li.innerHTML = `<span>${index + 1}위: ${rank.playerName}</span> <span>${minutes}:${seconds}</span>`;
|
||||
// rankingList.appendChild(li);
|
||||
// });
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error('랭킹 조회 중 오류 발생:', error);
|
||||
// rankingList.innerHTML = '<li>랭킹을 불러올 수 없습니다.</li>';
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * (★ 수정) user.js의 공통 submitRank 함수를 사용하도록 수정
|
||||
// */
|
||||
// submitRankBtn.addEventListener('click', async () => {
|
||||
// const userName = document.getElementById('username-input').value.trim();
|
||||
// if (!userName) return alert('이름을 입력해주세요.');
|
||||
//
|
||||
// try {
|
||||
// // (★ 수정) user.js의 공통 submitRank 함수 호출
|
||||
// // 스도쿠의 주 점수(primaryScore)는 완료 시간(secondsElapsed), 보조 점수는 없음.
|
||||
// await submitRank(currentGameType, currentContextId, userName, secondsElapsed, null);
|
||||
//
|
||||
// alert('랭킹이 성공적으로 등록되었습니다!');
|
||||
// showRankingModal(); // 랭킹 목록 새로고침
|
||||
// submitRankBtn.disabled = true; // 중복 등록 방지
|
||||
// } catch (error) {
|
||||
// console.error('랭킹 등록 중 오류 발생:', error);
|
||||
// alert('랭킹 등록에 실패했습니다. 다시 시도해주세요.');
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// function resetGameView() {
|
||||
// setupContainer.classList.remove('hidden');
|
||||
// gameContainer.classList.add('hidden');
|
||||
// numberInputButtons.classList.add('hidden');
|
||||
// clearInterval(timerInterval);
|
||||
// selectedNumber = null;
|
||||
// focusedCell = null;
|
||||
// document.querySelectorAll('.cell').forEach(cell => {
|
||||
// cell.classList.remove('highlight-focused', 'highlight-same-number', 'highlight-selected-number');
|
||||
// });
|
||||
// document.querySelectorAll('.num-btn').forEach(btn => btn.classList.remove('selected', 'completed'));
|
||||
// }
|
||||
//
|
||||
// closeModalBtn.addEventListener('click', () => {
|
||||
// modalOverlay.classList.add('hidden');
|
||||
// resetGameView();
|
||||
// });
|
||||
//
|
||||
// retryBtn.addEventListener('click', () => {
|
||||
// gameOverModal.classList.add('hidden');
|
||||
// resetGameView();
|
||||
// });
|
||||
// });₩
|
||||
@ -33,7 +33,12 @@
|
||||
<p th:if="${srcPost.writeTime != null and srcPost.writeTime > 0}"
|
||||
th:text="${#temporals.format(T(java.time.Instant).ofEpochMilli(srcPost.writeTime).atZone(T(java.time.ZoneId).systemDefault()).toLocalDateTime(), 'yyyy-MM-dd HH:mm:ss')}"></p>
|
||||
</header>
|
||||
|
||||
<div style="text-align: right; margin-bottom: 2em;">
|
||||
<label for="post-published-switch" style="font-weight: bold; color: #555; vertical-align: middle; margin-right: 10px;">
|
||||
게시물 공개
|
||||
</label>
|
||||
<input type="checkbox" id="post-published-switch" name="posting" th:checked="${srcPost.posting}" />
|
||||
</div>
|
||||
<div class="write_controllbox" style="margin-top: -1em; margin-bottom: 2em;">
|
||||
<div class="write_option btn-example controlbox-category" to="#popLayer1">
|
||||
</div>
|
||||
|
||||
@ -23,7 +23,13 @@
|
||||
</a>
|
||||
<div class="inner">
|
||||
<h3 style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span th:text="${post.title != null and not #strings.isEmpty(post.title)} ? ${post.title} : 'untitled[' + ${#temporals.format(T(java.time.Instant).ofEpochMilli(post.writeTime).atZone(T(java.time.ZoneId).systemDefault()).toLocalDateTime(), 'yyyy-MM-dd HH:mm')} + ']'"></span>
|
||||
|
||||
<span>
|
||||
<span th:if="${!post.posting}" style="background-color: #888; color: white; font-size: 0.7em; padding: 2px 6px; border-radius: 4px; margin-right: 8px; vertical-align: middle;">
|
||||
비공개
|
||||
</span>
|
||||
<span th:text="${post.title != null and not #strings.isEmpty(post.title)} ? ${post.title} : 'untitled[' + ${#temporals.format(T(java.time.Instant).ofEpochMilli(post.writeTime).atZone(T(java.time.ZoneId).systemDefault()).toLocalDateTime(), 'yyyy-MM-dd HH:mm')} + ']'"></span>
|
||||
</span>
|
||||
<span style="font-size: 0.75em; color: #888; font-weight: normal; white-space: nowrap; margin-left: 1em;">
|
||||
(읽음: <span th:text="${post.readCount}">0</span>)
|
||||
</span>
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
>
|
||||
<head>
|
||||
<link th:href="@{/css/common_game_theme.css}" rel="stylesheet" />
|
||||
<script type="text/javascript" th:src="@{/js/user.js}"></script>
|
||||
|
||||
<style>
|
||||
/* =================================
|
||||
@ -96,19 +95,21 @@
|
||||
.tile-4 { background-color: #bbdefb; color: #333; } /* #ede0c8 (노란 베이지) -> #bbdefb (파랑) */
|
||||
|
||||
/* 8부터는 고유 색상이므로 유지 (새 테마와 잘 어울림) */
|
||||
.tile-8 { background-color: #f2b179; color: #f9f6f2; }
|
||||
.tile-16 { background-color: #f59563; color: #f9f6f2; }
|
||||
.tile-32 { background-color: #f67c5f; color: #f9f6f2; }
|
||||
.tile-64 { background-color: #f65e3b; color: #f9f6f2; }
|
||||
.tile-128 { background-color: #edcf72; color: #f9f6f2; }
|
||||
.tile-256 { background-color: #edcc61; color: #f9f6f2; }
|
||||
.tile-512 { background-color: #edc850; color: #f9f6f2; }
|
||||
.tile-1024 { background-color: #edc53f; color: #f9f6f2; }
|
||||
.tile-2048 { background-color: #edc22e; color: #f9f6f2; }
|
||||
.tile-4096 { background-color: #3c3a32; color: #f9f6f2; }
|
||||
.tile-8192 { background-color: #ff3333; color: #f9f6f2; }
|
||||
.tile-16384 { background-color: #0077cc; color: #f9f6f2; }
|
||||
.tile-32768 { background-color: #9900cc; color: #f9f6f2; }
|
||||
.tile-2 { background-color: #E3F2FD; color: #333; } /* 아주 밝은 파랑 */
|
||||
.tile-4 { background-color: #BBDEFB; color: #333; } /* 밝은 파랑 */
|
||||
.tile-8 { background-color: #90CAF9; color: #fff; } /* 파랑 */
|
||||
.tile-16 { background-color: #64B5F6; color: #fff; } /* 조금 더 짙은 파랑 */
|
||||
.tile-32 { background-color: #42A5F5; color: #fff; } /* 짙은 파랑 */
|
||||
.tile-64 { background-color: #2196F3; color: #fff; } /* 선명한 파랑 */
|
||||
.tile-128 { background-color: #1E88E5; color: #fff; } /* 더 선명한 파랑 */
|
||||
.tile-256 { background-color: #1976D2; color: #fff; } /* 깊은 파랑 */
|
||||
.tile-512 { background-color: #1565C0; color: #fff; } /* 아주 깊은 파랑 */
|
||||
.tile-1024 { background-color: #0D47A1; color: #fff; } /* 남색에 가까운 파랑 */
|
||||
.tile-2048 { background-color: #283593; color: #fff; } /* 남색 */
|
||||
.tile-4096 { background-color: #3F51B5; color: #fff; } /* 인디고 */
|
||||
.tile-8192 { background-color: #673AB7; color: #fff; } /* 딥 퍼플 */
|
||||
.tile-16384 { background-color: #4527A0; color: #fff; } /* 더 짙은 딥 퍼플 */
|
||||
.tile-32768 { background-color: #311B92; color: #fff; } /* 가장 짙은 딥 퍼플 */
|
||||
|
||||
|
||||
/* =================================
|
||||
@ -186,29 +187,30 @@
|
||||
</head>
|
||||
<body>
|
||||
<th:block layout:fragment="content">
|
||||
<h1>2048</h1>
|
||||
<p>화살표를 터치하거나 키보드 방향키를 이용해 타일을 합쳐보세요!</p>
|
||||
<div class="game-container">
|
||||
<div class="score-container">
|
||||
<strong>점수:</strong> <span id="score">0</span>
|
||||
</div>
|
||||
<div id="game-board"></div>
|
||||
|
||||
<div id="game-over-popup" class="popup-container" style="display:none;">
|
||||
<div class="popup">
|
||||
<h2>게임 오버!</h2>
|
||||
<p>최종 점수: <span id="final-score">0</span></p>
|
||||
<input type="text" id="player-name" placeholder="이름을 입력하세요">
|
||||
<button id="save-score">점수 저장</button>
|
||||
<div class="game-body-wrapper"> <h1>2048</h1>
|
||||
<p>화살표를 터치하거나 키보드 방향키를 이용해 타일을 합쳐보세요!</p>
|
||||
<div class="game-container">
|
||||
<div class="score-container">
|
||||
<strong>점수:</strong> <span id="score">0</span>
|
||||
</div>
|
||||
<div id="game-board"></div>
|
||||
|
||||
<div id="game-over-popup" class="popup-container" style="display:none;">
|
||||
<div class="popup">
|
||||
<h2>게임 오버!</h2>
|
||||
<p>최종 점수: <span id="final-score">0</span></p>
|
||||
<input type="text" id="player-name" placeholder="이름을 입력하세요">
|
||||
<button id="save-score">점수 저장</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ranking-container">
|
||||
<h3>🏆 랭킹</h3>
|
||||
<ol id="ranking-list"></ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ranking-container">
|
||||
<h3>🏆 랭킹</h3>
|
||||
<ol id="ranking-list"></ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// ... (DOM 요소 가져오기 - 동일)
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
layout:decorate="~{layout/default_layout}"
|
||||
>
|
||||
<th:block layout:fragment="head" id="head">
|
||||
<script type="text/javascript" th:src="@{/js/user.js}"></script>
|
||||
<link th:href="@{/css/common_game_theme.css}" rel="stylesheet" />
|
||||
|
||||
<style>
|
||||
@ -1065,8 +1064,10 @@
|
||||
|
||||
</th:block >
|
||||
<th:block layout:fragment="content">
|
||||
<div class="game-body-wrapper">
|
||||
<div id="game-container">
|
||||
<canvas id="gameCanvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
</html>
|
||||
@ -7,8 +7,6 @@
|
||||
>
|
||||
<head layout:fragment="head" id="head">
|
||||
<link th:href="@{/css/common_game_theme.css}" rel="stylesheet" />
|
||||
<script type="text/javascript" th:src="@{/js/user.js}"></script>
|
||||
|
||||
<style>
|
||||
/* sudoku.css의 내용을 여기에 삽입 */
|
||||
body {
|
||||
@ -614,7 +612,7 @@
|
||||
try {
|
||||
// user.js의 공통 fetchRanks 함수 호출 (스도쿠 퍼즐 ID 전달)
|
||||
// currentGameType 변수가 정의되어 있어야 합니다. 예: const currentGameType = 'sudoku';
|
||||
const currentGameType = 'sudoku';
|
||||
const currentGameType = 'SUDOKU';
|
||||
const rankings = await fetchRanks(currentGameType, currentPuzzleId);
|
||||
|
||||
rankingList.innerHTML = '';
|
||||
@ -644,7 +642,7 @@
|
||||
|
||||
try {
|
||||
// user.js의 공통 submitRank 함수 호출
|
||||
const currentGameType = 'sudoku';
|
||||
const currentGameType = 'SUDOKU';
|
||||
await submitRank(currentGameType, currentPuzzleId, userName, secondsElapsed, null);
|
||||
|
||||
alert('랭킹이 성공적으로 등록되었습니다!');
|
||||
|
||||
@ -6,25 +6,145 @@
|
||||
layout:decorate="~{layout/default_layout}"
|
||||
>
|
||||
<th:block layout:fragment="head" id="head">
|
||||
<script type="text/javascript" th:src="@{/js/admin-sudoku.js}"></script>
|
||||
<!-- <link th:href="@{/css/puzzle.css}" rel="stylesheet" />-->
|
||||
<link th:href="@{/css/admin-sudoku.css}" rel="stylesheet" />
|
||||
<style>
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f4f7f9;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#generate-btn {
|
||||
padding: 12px 24px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
#generate-btn:disabled {
|
||||
background-color: #cccccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#generate-btn:hover:not(:disabled) {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
#status-message {
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#sudoku-board {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(9, 40px);
|
||||
grid-template-rows: repeat(9, 40px);
|
||||
border: 3px solid #333;
|
||||
margin: 20px auto 0;
|
||||
width: 366px; /* (40px * 9) + 6px border */
|
||||
}
|
||||
|
||||
.cell {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 1px solid #ccc;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 3x3 구역을 위한 굵은 선 */
|
||||
.cell:nth-child(3n) { border-right: 2px solid #333; }
|
||||
.cell:nth-child(9n) { border-right: none; }
|
||||
.cell:nth-of-type(n+19):nth-of-type(-n+27),
|
||||
.cell:nth-of-type(n+46):nth-of-type(-n+54) {
|
||||
border-bottom: 2px solid #333;
|
||||
}
|
||||
</style>
|
||||
<script th:inline="javascript">
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const generateBtn = document.getElementById('generate-btn');
|
||||
const boardElement = document.getElementById('sudoku-board');
|
||||
const statusMessage = document.getElementById('status-message');
|
||||
|
||||
// 스도쿠 보드를 화면에 그리는 함수
|
||||
function renderBoard(puzzleString) {
|
||||
boardElement.innerHTML = ''; // 기존 보드 초기화
|
||||
if (!puzzleString || puzzleString.length !== 81) {
|
||||
statusMessage.textContent = '잘못된 퍼즐 데이터입니다.';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const char of puzzleString) {
|
||||
const cell = document.createElement('div');
|
||||
cell.classList.add('cell');
|
||||
cell.textContent = char;
|
||||
boardElement.appendChild(cell);
|
||||
}
|
||||
}
|
||||
|
||||
// 생성 버튼 클릭 이벤트 리스너
|
||||
generateBtn.addEventListener('click', async () => {
|
||||
// 버튼 비활성화 및 상태 메시지 업데이트
|
||||
generateBtn.disabled = true;
|
||||
statusMessage.textContent = '새로운 스도쿠 퍼즐을 생성 중입니다... 🧠';
|
||||
boardElement.innerHTML = ''; // 보드 비우기
|
||||
|
||||
try {
|
||||
// 백엔드 API 호출 (POST 요청)
|
||||
const response = await fetch('/puzzle/sudoku/sudoku_gen');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`서버 오류: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// 성공 시 보드 렌더링
|
||||
renderBoard(result.puzzle);
|
||||
statusMessage.textContent = `✅ 퍼즐 생성 완료! (ID: ${result.puzzleKey})`;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error generating Sudoku:', error);
|
||||
statusMessage.textContent = `❌ 생성 실패: ${error.message}`;
|
||||
} finally {
|
||||
// 성공/실패 여부와 관계없이 버튼 다시 활성화
|
||||
generateBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script type="text/javascript" th:src="@{/js/user.js}"></script>
|
||||
</th:block >
|
||||
<th:block layout:fragment="content">
|
||||
<div class="container">
|
||||
<h1>스도쿠 퍼즐 생성기</h1>
|
||||
<p>버튼을 누르면 새로운 스도쿠 퍼즐을 생성하여 DB에 저장하고 화면에 보여줍니다.</p>
|
||||
<button id="generate-btn">새 스도쿠 생성</button>
|
||||
<div id="status-message"></div>
|
||||
<h1>스도쿠 퍼즐 생성기</h1>
|
||||
<p>버튼을 누르면 새로운 스도쿠 퍼즐을 생성하여 DB에 저장하고 화면에 보여줍니다.</p>
|
||||
<button id="generate-btn">새 스도쿠 생성</button>
|
||||
<div id="status-message"></div>
|
||||
|
||||
<h2>생성된 퍼즐</h2>
|
||||
<div id="sudoku-board">
|
||||
</div>
|
||||
<h2>생성된 퍼즐</h2>
|
||||
<div id="sudoku-board">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</th:block>
|
||||
|
||||
@ -6,12 +6,183 @@
|
||||
layout:decorate="~{layout/default_layout}"
|
||||
>
|
||||
<th:block layout:fragment="head" id="head">
|
||||
<script type="text/javascript" th:src="@{/js/nonogram.js}"></script>
|
||||
<link th:href="@{/css/nonogram.css}" rel="stylesheet" />
|
||||
<script th:inline="javascript">
|
||||
/**
|
||||
* ==============================================
|
||||
* upload.js (업로드 페이지 로직)
|
||||
* (★ 리팩토링: 통합 API 경로 사용)
|
||||
* ==============================================
|
||||
*/
|
||||
let currentPuzzleData = null; // 업로드 성공 시 퍼즐 데이터 저장
|
||||
|
||||
// (★ 수정 없음) 업로드 페이지용 성공 애니메이션 함수
|
||||
function showSuccessAnimation() {
|
||||
if (!currentPuzzleData) return;
|
||||
|
||||
const puzzleContainer = document.getElementById('puzzle-container');
|
||||
const grayscaleImg = document.getElementById('grayscale-reveal');
|
||||
const originalImg = document.getElementById('original-reveal');
|
||||
|
||||
grayscaleImg.src = currentPuzzleData.grayscaleImage;
|
||||
originalImg.src = currentPuzzleData.originalImage;
|
||||
|
||||
puzzleContainer.style.transition = 'opacity 0.5s';
|
||||
puzzleContainer.style.opacity = '0';
|
||||
grayscaleImg.style.opacity = '1';
|
||||
|
||||
setTimeout(() => {
|
||||
grayscaleImg.style.opacity = '0';
|
||||
originalImg.style.opacity = '1';
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// (★ 수정 없음) 업로드 페이지용 퍼즐 미리보기 그리기 함수
|
||||
function drawPuzzle(puzzleData) {
|
||||
const container = document.getElementById('puzzle-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
const { solutionGrid, rowClues, colClues } = puzzleData;
|
||||
const numRows = solutionGrid.length;
|
||||
const numCols = solutionGrid[0].length;
|
||||
|
||||
container.style.gridTemplateColumns = `auto repeat(${numCols}, 1fr)`;
|
||||
container.style.gridTemplateRows = `auto repeat(${numRows}, 1fr)`;
|
||||
|
||||
// 1. 코너
|
||||
const corner = document.createElement('div');
|
||||
corner.className = 'grid-cell';
|
||||
container.appendChild(corner);
|
||||
|
||||
// 2. 열 힌트
|
||||
for (const clues of colClues) {
|
||||
const clueCell = document.createElement('div');
|
||||
clueCell.className = 'clue-cell';
|
||||
clueCell.innerHTML = clues.join('<br>');
|
||||
container.appendChild(clueCell);
|
||||
}
|
||||
|
||||
// 3. 행 힌트 및 정답 그리드
|
||||
for (let i = 0; i < numRows; i++) {
|
||||
const rowClueCell = document.createElement('div');
|
||||
rowClueCell.className = 'clue-cell';
|
||||
rowClueCell.textContent = rowClues[i].join(' ');
|
||||
container.appendChild(rowClueCell);
|
||||
|
||||
for (let j = 0; j < numCols; j++) {
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'solution-cell';
|
||||
if (solutionGrid[i][j] === 1) {
|
||||
cell.classList.add('filled');
|
||||
} else {
|
||||
cell.classList.add('empty');
|
||||
}
|
||||
container.appendChild(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// upload.js의 DOMContentLoaded 리스너
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const createBtn = document.getElementById('createBtn');
|
||||
|
||||
// createBtn이 없는 nonogram.html(게임 페이지)에서는 이 리스너가 아무것도 실행하지 않음.
|
||||
if (!createBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
// (업로드 페이지 전용 로직)
|
||||
createBtn.addEventListener('click', async () => {
|
||||
const uploader = document.getElementById('imageUploader');
|
||||
const statusDiv = document.getElementById('status');
|
||||
const puzzleContainer = document.getElementById('puzzle-container');
|
||||
const testSuccessBtn = document.getElementById('test-success-btn');
|
||||
const deleteBtn = document.getElementById('delete-btn');
|
||||
const playBtn = document.getElementById('play-btn');
|
||||
|
||||
if (uploader.files.length === 0) {
|
||||
statusDiv.textContent = '이미지 파일을 선택해주세요.';
|
||||
return;
|
||||
}
|
||||
|
||||
const imageFile = uploader.files[0];
|
||||
const formData = new FormData();
|
||||
formData.append('imageFile', imageFile);
|
||||
|
||||
statusDiv.textContent = '문제를 생성하는 중...';
|
||||
puzzleContainer.innerHTML = '';
|
||||
|
||||
try {
|
||||
// (★ 수정) API 경로 변경 -> 통합 컨트롤러의 /puzzle/upload.bjx 호출
|
||||
const response = await fetch('/puzzle/upload.bjx', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const puzzleData = await response.json();
|
||||
statusDiv.textContent = '문제 생성 성공!';
|
||||
drawPuzzle(puzzleData); // 미리보기 그리기
|
||||
|
||||
currentPuzzleData = puzzleData;
|
||||
testSuccessBtn.addEventListener('click', showSuccessAnimation);
|
||||
|
||||
testSuccessBtn.style.display = 'inline-block';
|
||||
deleteBtn.style.display = 'inline-block';
|
||||
playBtn.style.display = 'inline-block';
|
||||
} else {
|
||||
const errorMessage = await response.text();
|
||||
statusDiv.textContent = `생성 실패: ${errorMessage}`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('네트워크 오류:', error);
|
||||
statusDiv.textContent = '서버와 통신 중 오류가 발생했습니다.';
|
||||
}
|
||||
|
||||
deleteBtn.addEventListener('click', async () => {
|
||||
if (!currentPuzzleData || !currentPuzzleData.id) {
|
||||
alert('삭제할 퍼즐이 선택되지 않았습니다.');
|
||||
return;
|
||||
}
|
||||
if (!confirm('정말로 이 퍼즐을 삭제하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// (★ 수정) API 경로 변경 -> 통합 컨트롤러의 /puzzle/{id}.bjx 호출
|
||||
const response = await fetch(`/puzzle/${currentPuzzleData.id}.bjx`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
statusDiv.textContent = '퍼즐이 성공적으로 삭제되었습니다.';
|
||||
puzzleContainer.innerHTML = '';
|
||||
// (버그 수정) success-animation-container 내부의 img src를 초기화해야 함
|
||||
document.getElementById('grayscale-reveal').src = "";
|
||||
document.getElementById('original-reveal').src = "";
|
||||
|
||||
testSuccessBtn.style.display = 'none';
|
||||
deleteBtn.style.display = 'none';
|
||||
playBtn.style.display = 'none';
|
||||
currentPuzzleData = null;
|
||||
} else {
|
||||
statusDiv.textContent = `삭제 실패: 서버 오류 (${response.status})`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('삭제 중 네트워크 오류:', error);
|
||||
statusDiv.textContent = '삭제 중 오류가 발생했습니다.';
|
||||
}
|
||||
});
|
||||
|
||||
playBtn.addEventListener('click', () => {
|
||||
if (currentPuzzleData && currentPuzzleData.id) {
|
||||
// (★ 수정 없음) 이 경로는 PuzzleController의 페이지 서빙 경로와 일치하므로 올바름.
|
||||
window.location.href = `/puzzle/play/${currentPuzzleData.id}`;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script type="text/javascript" th:src="@{/js/user.js}"></script>
|
||||
</th:block >
|
||||
<th:block layout:fragment="content">
|
||||
<div id="puzzle-container">
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
onclickJoin([[${enc}]],[[${keyword}]])
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" th:src="@{/js/user.js}"></script>
|
||||
</th:block >
|
||||
<th:block layout:fragment="content">
|
||||
<table id="main_layer">
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
<head>
|
||||
<title>Spring Boot</title>
|
||||
|
||||
<script type="text/javascript" th:src="@{/js/user.js}"></script>
|
||||
</head>>
|
||||
<body onload="checkDebug()">
|
||||
<th:block layout:fragment="header" th:include="@{fragments/header}"></th:block>
|
||||
|
||||
@ -53,7 +53,6 @@
|
||||
readCount: [[${srcPost?.readCount ?: 0}]],
|
||||
voteCount: [[${srcPost?.voteCount ?: 0}]],
|
||||
unlikeCount: [[${srcPost?.unlikeCount ?: 0}]],
|
||||
|
||||
// --- Page-specific (모델 데이터 아님) ---
|
||||
enc: /*[[${enc ?: ''}]]*/,
|
||||
keyword: /*[[${keyword ?: ''}]]*/
|
||||
|
||||
@ -59,7 +59,6 @@
|
||||
<script th:src="@{/js/jquery.min.js}"></script>
|
||||
|
||||
<script th:src="@{/js/common.js}"></script>
|
||||
<script th:src="@{/js/user}"></script>
|
||||
<script th:src="@{/js/jquery.dropotron.min.js}"></script>
|
||||
<script th:src="@{/js/browser.min.js}"></script>
|
||||
<script th:src="@{/js/breakpoints.min.js}"></script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user