Initial commit
30
.github/workflows/classroom.yml
vendored
|
|
@ -1,30 +0,0 @@
|
||||||
name: Autograding Tests
|
|
||||||
'on':
|
|
||||||
- push
|
|
||||||
- repository_dispatch
|
|
||||||
permissions:
|
|
||||||
checks: write
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
jobs:
|
|
||||||
run-autograding-tests:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.actor != 'github-classroom[bot]'
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Unit Tests
|
|
||||||
id: unit-tests
|
|
||||||
uses: classroom-resources/autograding-command-grader@v1
|
|
||||||
with:
|
|
||||||
test-name: Unit Tests
|
|
||||||
setup-command: ''
|
|
||||||
command: make test
|
|
||||||
timeout: 10
|
|
||||||
max-score: 80
|
|
||||||
- name: Autograding Reporter
|
|
||||||
uses: classroom-resources/autograding-grading-reporter@v1
|
|
||||||
env:
|
|
||||||
UNIT-TESTS_RESULTS: "${{steps.unit-tests.outputs.result}}"
|
|
||||||
with:
|
|
||||||
runners: unit-tests
|
|
||||||
85
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
# specific name of executable generated
|
||||||
|
toh_main_driver
|
||||||
|
toh_test_driver
|
||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/c++,macos,linux
|
||||||
|
# Edit at https://www.gitignore.io/?templates=c++,macos,linux
|
||||||
|
|
||||||
|
### C++ ###
|
||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Compiled Object files
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# Fortran module files
|
||||||
|
*.mod
|
||||||
|
*.smod
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
|
||||||
|
### Linux ###
|
||||||
|
*~
|
||||||
|
|
||||||
|
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||||
|
.fuse_hidden*
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Linux trash folder which might appear on any partition or disk
|
||||||
|
.Trash-*
|
||||||
|
|
||||||
|
# .nfs files are created when an open file is removed but is still being accessed
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
### macOS ###
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/c++,macos,linux
|
||||||
5
README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
### if.03.22 Procedural Programming
|
||||||
|
# Assignment – Recursion and Abstract Data Type
|
||||||
|
## Mine Sweeper
|
||||||
|
With this assignment you shall implement abstract data types and a recursive algorithm in C.
|
||||||
|
Clone this assignment, open the index.html, read the assignment instructions and try to make all unit tests green.
|
||||||
18
config.h
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: n/a
|
||||||
|
* Title: Configuration Options
|
||||||
|
* Author: S. Schraml
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Global application configuration options
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
#ifndef ___CONFIGURATION_H
|
||||||
|
#define ___CONFIGURATION_H
|
||||||
|
|
||||||
|
/** The maximum size of the board dimension (used for columns and rows). */
|
||||||
|
#define MAX_BOARD_SIZE 26
|
||||||
|
|
||||||
|
#endif
|
||||||
14
doxygen_extra.css
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
code {
|
||||||
|
background: #eaeaea;
|
||||||
|
padding: 0 0.3em;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
div.fragment {
|
||||||
|
padding: 0.5em;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.line {
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
62
general.h
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: n/a
|
||||||
|
* Title: general.h
|
||||||
|
* Author: P. Bauer, S. Schraml, */<your name>/*
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* General usable definitions and types.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ___GENERAL_H
|
||||||
|
#define ___GENERAL_H
|
||||||
|
|
||||||
|
/** A very range-limited integer type */
|
||||||
|
typedef unsigned char Byte;
|
||||||
|
|
||||||
|
/** The type for various counters of game entities (rows, columns, mines) */
|
||||||
|
typedef unsigned short Count;
|
||||||
|
|
||||||
|
/** The type for cell indexes, to ensure type compatibility, the abstract base type is used */
|
||||||
|
typedef Count CellIdx;
|
||||||
|
|
||||||
|
/** The type for addressing board columns on UI (user input, presentation). */
|
||||||
|
typedef unsigned short ColAddr;
|
||||||
|
|
||||||
|
/** The type for addressing board rows on UI (user input, presentation). */
|
||||||
|
typedef char RowAddr;
|
||||||
|
|
||||||
|
/** CellMarker: The enumeration of cell markers. */
|
||||||
|
NONE, /* cell is not marked */
|
||||||
|
MINE_DETECTED, /* cell carries a mine */
|
||||||
|
MINE_SUSPECTED /* cell is suspected to carry a mine */
|
||||||
|
|
||||||
|
/** GameMode: The enumeration of defined game modes (sizes) */
|
||||||
|
BEGINNER, /* a 'small' game, e.g. 8x8 cells having a low number of mines (e.g. 10) */
|
||||||
|
ADVANCED, /* a 'medium' game, e.g. 16x16 cells having a medium number of mines (e.g. 40) */
|
||||||
|
EXPERT, /* a 'large' game, e.g. 26x18 cells having a high number of mines (e.g. 99) */
|
||||||
|
CUSTOM, /* a customized game, number of columns, rows, and mines are defined by the user */
|
||||||
|
|
||||||
|
/** GameState: The enumeration of game states. */
|
||||||
|
INVALID, /* default state */
|
||||||
|
IN_PROGRESS, /* the game is currently played */
|
||||||
|
SOLVED, /* the game was successfully completed */
|
||||||
|
FAILED /* the game was not successfully completed */
|
||||||
|
|
||||||
|
/** Action: The enumeration of possible user input actions. */
|
||||||
|
UNKNOWN, /* default state, input was not recognized */
|
||||||
|
CELL_SELECT, /* the user entered a (possibly invalid) cell address. */
|
||||||
|
MARK_MINE, /* the action to mark the selected cell having a mine */
|
||||||
|
MARK_SUSPECT, /* the action to mark the selected cell being suspected having a mine */
|
||||||
|
CLEAR_MARKER, /* the action for removing a marker on the selected cell */
|
||||||
|
UNCOVER, /* the action to uncover a (possibly) selected cell */
|
||||||
|
QUIT_GAME, /* the action to terminate the game */
|
||||||
|
|
||||||
|
/** Convenience macro do get maximum of two numbers */
|
||||||
|
#define MAX(x, y) ((x) > (y) ? (x) : (y))
|
||||||
|
/** Convenience macro do get maximum of two numbers */
|
||||||
|
#define MIN(x, y) ((x) < (y) ? (x) : (y))
|
||||||
|
|
||||||
|
#endif
|
||||||
BIN
html/bc_s.png
Normal file
|
After Width: | Height: | Size: 676 B |
BIN
html/bdwn.png
Normal file
|
After Width: | Height: | Size: 147 B |
BIN
html/closed.png
Normal file
|
After Width: | Height: | Size: 132 B |
BIN
html/doc.png
Normal file
|
After Width: | Height: | Size: 746 B |
1730
html/doxygen.css
Normal file
BIN
html/doxygen.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
14
html/doxygen_extra.css
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
code {
|
||||||
|
background: #eaeaea;
|
||||||
|
padding: 0 0.3em;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
div.fragment {
|
||||||
|
padding: 0.5em;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.line {
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
121
html/dynsections.js
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
@licstart The following is the entire license notice for the JavaScript code in this file.
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (C) 1997-2020 by Dimitri van Heesch
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||||
|
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||||
|
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||||
|
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
@licend The above is the entire license notice for the JavaScript code in this file
|
||||||
|
*/
|
||||||
|
function toggleVisibility(linkObj)
|
||||||
|
{
|
||||||
|
var base = $(linkObj).attr('id');
|
||||||
|
var summary = $('#'+base+'-summary');
|
||||||
|
var content = $('#'+base+'-content');
|
||||||
|
var trigger = $('#'+base+'-trigger');
|
||||||
|
var src=$(trigger).attr('src');
|
||||||
|
if (content.is(':visible')===true) {
|
||||||
|
content.hide();
|
||||||
|
summary.show();
|
||||||
|
$(linkObj).addClass('closed').removeClass('opened');
|
||||||
|
$(trigger).attr('src',src.substring(0,src.length-8)+'closed.png');
|
||||||
|
} else {
|
||||||
|
content.show();
|
||||||
|
summary.hide();
|
||||||
|
$(linkObj).removeClass('closed').addClass('opened');
|
||||||
|
$(trigger).attr('src',src.substring(0,src.length-10)+'open.png');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStripes()
|
||||||
|
{
|
||||||
|
$('table.directory tr').
|
||||||
|
removeClass('even').filter(':visible:even').addClass('even');
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleLevel(level)
|
||||||
|
{
|
||||||
|
$('table.directory tr').each(function() {
|
||||||
|
var l = this.id.split('_').length-1;
|
||||||
|
var i = $('#img'+this.id.substring(3));
|
||||||
|
var a = $('#arr'+this.id.substring(3));
|
||||||
|
if (l<level+1) {
|
||||||
|
i.removeClass('iconfopen iconfclosed').addClass('iconfopen');
|
||||||
|
a.html('▼');
|
||||||
|
$(this).show();
|
||||||
|
} else if (l==level+1) {
|
||||||
|
i.removeClass('iconfclosed iconfopen').addClass('iconfclosed');
|
||||||
|
a.html('►');
|
||||||
|
$(this).show();
|
||||||
|
} else {
|
||||||
|
$(this).hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateStripes();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFolder(id)
|
||||||
|
{
|
||||||
|
// the clicked row
|
||||||
|
var currentRow = $('#row_'+id);
|
||||||
|
|
||||||
|
// all rows after the clicked row
|
||||||
|
var rows = currentRow.nextAll("tr");
|
||||||
|
|
||||||
|
var re = new RegExp('^row_'+id+'\\d+_$', "i"); //only one sub
|
||||||
|
|
||||||
|
// only match elements AFTER this one (can't hide elements before)
|
||||||
|
var childRows = rows.filter(function() { return this.id.match(re); });
|
||||||
|
|
||||||
|
// first row is visible we are HIDING
|
||||||
|
if (childRows.filter(':first').is(':visible')===true) {
|
||||||
|
// replace down arrow by right arrow for current row
|
||||||
|
var currentRowSpans = currentRow.find("span");
|
||||||
|
currentRowSpans.filter(".iconfopen").removeClass("iconfopen").addClass("iconfclosed");
|
||||||
|
currentRowSpans.filter(".arrow").html('►');
|
||||||
|
rows.filter("[id^=row_"+id+"]").hide(); // hide all children
|
||||||
|
} else { // we are SHOWING
|
||||||
|
// replace right arrow by down arrow for current row
|
||||||
|
var currentRowSpans = currentRow.find("span");
|
||||||
|
currentRowSpans.filter(".iconfclosed").removeClass("iconfclosed").addClass("iconfopen");
|
||||||
|
currentRowSpans.filter(".arrow").html('▼');
|
||||||
|
// replace down arrows by right arrows for child rows
|
||||||
|
var childRowsSpans = childRows.find("span");
|
||||||
|
childRowsSpans.filter(".iconfopen").removeClass("iconfopen").addClass("iconfclosed");
|
||||||
|
childRowsSpans.filter(".arrow").html('►');
|
||||||
|
childRows.show(); //show all children
|
||||||
|
}
|
||||||
|
updateStripes();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function toggleInherit(id)
|
||||||
|
{
|
||||||
|
var rows = $('tr.inherit.'+id);
|
||||||
|
var img = $('tr.inherit_header.'+id+' img');
|
||||||
|
var src = $(img).attr('src');
|
||||||
|
if (rows.filter(':first').is(':visible')===true) {
|
||||||
|
rows.css('display','none');
|
||||||
|
$(img).attr('src',src.substring(0,src.length-8)+'closed.png');
|
||||||
|
} else {
|
||||||
|
rows.css('display','table-row'); // using show() causes jump in firefox
|
||||||
|
$(img).attr('src',src.substring(0,src.length-10)+'open.png');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* @license-end */
|
||||||
82
html/files.html
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||||
|
<meta name="generator" content="Doxygen 1.8.18"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<title>Mine Sweeper: File List</title>
|
||||||
|
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||||
|
<script type="text/javascript" src="jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="dynsections.js"></script>
|
||||||
|
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||||
|
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||||
|
<script type="text/javascript" src="search/search.js"></script>
|
||||||
|
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="doxygen_extra.css" rel="stylesheet" type="text/css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||||
|
<div id="titlearea">
|
||||||
|
<table cellspacing="0" cellpadding="0">
|
||||||
|
<tbody>
|
||||||
|
<tr style="height: 56px;">
|
||||||
|
<td id="projectalign" style="padding-left: 0.5em;">
|
||||||
|
<div id="projectname">Mine Sweeper
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- end header part -->
|
||||||
|
<!-- Generated by Doxygen 1.8.18 -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||||
|
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||||
|
/* @license-end */
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="menudata.js"></script>
|
||||||
|
<script type="text/javascript" src="menu.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||||
|
$(function() {
|
||||||
|
initMenu('',true,false,'search.php','Search');
|
||||||
|
$(document).ready(function() { init_search(); });
|
||||||
|
});
|
||||||
|
/* @license-end */</script>
|
||||||
|
<div id="main-nav"></div>
|
||||||
|
</div><!-- top -->
|
||||||
|
<!-- window showing the filter options -->
|
||||||
|
<div id="MSearchSelectWindow"
|
||||||
|
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||||
|
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||||
|
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- iframe showing the search results (closed by default) -->
|
||||||
|
<div id="MSearchResultsWindow">
|
||||||
|
<iframe src="javascript:void(0)" frameborder="0"
|
||||||
|
name="MSearchResults" id="MSearchResults">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<div class="headertitle">
|
||||||
|
<div class="title">File List</div> </div>
|
||||||
|
</div><!--header-->
|
||||||
|
<div class="contents">
|
||||||
|
<div class="textblock">Here is a list of all files with brief descriptions:</div><div class="directory">
|
||||||
|
<table class="directory">
|
||||||
|
<tr id="row_0_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><a href="mainpage_8h_source.html"><span class="icondoc"></span></a><a class="el" href="mainpage_8h.html" target="_self">mainpage.h</a></td><td class="desc"></td></tr>
|
||||||
|
</table>
|
||||||
|
</div><!-- directory -->
|
||||||
|
</div><!-- contents -->
|
||||||
|
<!-- start footer part -->
|
||||||
|
<hr class="footer"/><address class="footer"><small>
|
||||||
|
Generated on Sun Dec 27 2020 21:16:06 for Mine Sweeper by  <a href="http://www.doxygen.org/index.html">
|
||||||
|
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
||||||
|
</a> 1.8.18
|
||||||
|
</small></address>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
html/folderclosed.png
Normal file
|
After Width: | Height: | Size: 616 B |
BIN
html/folderopen.png
Normal file
|
After Width: | Height: | Size: 597 B |
141
html/index.html
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||||
|
<meta name="generator" content="Doxygen 1.8.18"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<title>Mine Sweeper: Main Page</title>
|
||||||
|
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||||
|
<script type="text/javascript" src="jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="dynsections.js"></script>
|
||||||
|
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||||
|
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||||
|
<script type="text/javascript" src="search/search.js"></script>
|
||||||
|
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="doxygen_extra.css" rel="stylesheet" type="text/css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||||
|
<div id="titlearea">
|
||||||
|
<table cellspacing="0" cellpadding="0">
|
||||||
|
<tbody>
|
||||||
|
<tr style="height: 56px;">
|
||||||
|
<td id="projectalign" style="padding-left: 0.5em;">
|
||||||
|
<div id="projectname">Mine Sweeper
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- end header part -->
|
||||||
|
<!-- Generated by Doxygen 1.8.18 -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||||
|
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||||
|
/* @license-end */
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="menudata.js"></script>
|
||||||
|
<script type="text/javascript" src="menu.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||||
|
$(function() {
|
||||||
|
initMenu('',true,false,'search.php','Search');
|
||||||
|
$(document).ready(function() { init_search(); });
|
||||||
|
});
|
||||||
|
/* @license-end */</script>
|
||||||
|
<div id="main-nav"></div>
|
||||||
|
</div><!-- top -->
|
||||||
|
<!-- window showing the filter options -->
|
||||||
|
<div id="MSearchSelectWindow"
|
||||||
|
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||||
|
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||||
|
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- iframe showing the search results (closed by default) -->
|
||||||
|
<div id="MSearchResultsWindow">
|
||||||
|
<iframe src="javascript:void(0)" frameborder="0"
|
||||||
|
name="MSearchResults" id="MSearchResults">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="PageDoc"><div class="header">
|
||||||
|
<div class="headertitle">
|
||||||
|
<div class="title">Mine Sweeper Documentation</div> </div>
|
||||||
|
</div><!--header-->
|
||||||
|
<div class="contents">
|
||||||
|
<div class="textblock"><h1><a class="anchor" id="intro"></a>
|
||||||
|
Introduction</h1>
|
||||||
|
<p>The console version of the popular 'Mine Sweeper' game.</p>
|
||||||
|
<p>The board consists of initially covered cells. Some of these cells carries mines.</p>
|
||||||
|
<p>Cells can be marked as having a mine (mine detected) or as being suspected of carrying a mine (mine suspected).</p>
|
||||||
|
<p>Cells can be uncovered to reveal their content. Cells may show a number after they are uncovered. This number tells how many of their eight direct neighbor cells carries a mine and are therefore 'dangerous neighbors'. If a cell without dangerous neighbors is uncovered, all neighbor cells of that cell are uncovered as well. If such a neighbor cell again has no dangerous neighbors, all neighbor cells of this cell are uncovered also, and so on.</p>
|
||||||
|
<p>The objective of the game is find all cells carrying a mine without uncovering those. The game is won, if each cell carrying a mine is marked as such (mine detected) AND all other cells are uncovered. If a cell that carries a mine is uncovered, the game is last as the mine detonates.</p>
|
||||||
|
<p>Cells are addressed by their column and row coordinate. Before any action can be taken on a cell, it need to be selected first. Afterwards actions such as setting or clearing markers as well as uncovering the cell can be performed. Alternatively a different cell can be selected. This approach shall avoid performing an action on a 'wrong' cell.</p>
|
||||||
|
<h1><a class="anchor" id="objective"></a>
|
||||||
|
Assignment Objective</h1>
|
||||||
|
<p>In this assignment the required Abstract Data Types and the game logic as well as user interface interaction shall be implemented. A visualizer for the complete game is provided and shall be used. The visualizer is also capable of capturing user inputs and provides the captured result.</p>
|
||||||
|
<p>The 'main loop' of the application shall be implemented in <code>ms_main_driver.c</code>, function <code>ui_branch</code>.</p>
|
||||||
|
<p>That main loop shall: #Start a game #visualize the initial game using <code>ms_visualizer</code> #evaluate the user input provided after <code>ms_visualize()</code> returns #perform the desired action, e.g. selecting a cell, set a marker, or quit the game #continue with step 2 until the captured action is 'quit' or the game is over (solved or failed)</p>
|
||||||
|
<p>The minimal main loop described above may be extended (surrounded) by an outer loop, which allows to restart a game in a different mode.</p>
|
||||||
|
<p>Note that <code>ms_visualizer</code> supported a 'cheat' mode, which shows the content of covered cells. This may be helpful during development.</p>
|
||||||
|
<p><b>Configuration</b></p>
|
||||||
|
<p>Because we operate on static memory, the maximum size of the board in each dimension can be configured in <code>config.h</code>.</p>
|
||||||
|
<p><b>Setup and Execution</b></p>
|
||||||
|
<p>Initially the board has the set number of cells, all cells are covered. No cell carries a mine at this point in time. After the user uncovers the first cell, the mines are distributes and the number of dangerous neighbors is calculated. The first uncovered cell shall be empty (and therefore uncover neighbor cells, as described above) for sure, to avoid ambiguous configuration on the first action.</p>
|
||||||
|
<p><b>Visualization</b></p>
|
||||||
|
<p>As described above, the visualization library realized by <code>ms_visualizer</code> is capable to render the complete game, including status bar and user prompts as well as capturing user input. User interface related texts and symbols can be more or less freely defined in <code>ms_ui_utils.c</code>. The visualizer does not make assumptions on marker presentation or input keys.</p>
|
||||||
|
<h1><a class="anchor" id="assignment"></a>
|
||||||
|
Assignment</h1>
|
||||||
|
<p>This is a rather complex assignment, therefore it is vital, to develop the code in a focused and clean way, step by step. Many unit tests are provided to ease development. Some unit tests provide also helpful debugging traces. If too many failed unit tests appear - especially at the beginning of development- they can be commented in file <code>ms_test_driver.c</code> for files that are currently not in focus. However, they need to be reactivated as development proceeds.</p>
|
||||||
|
<p>Hint: Read and obey the comment within the code.</p>
|
||||||
|
<ol type="1">
|
||||||
|
<li>Complete<ul>
|
||||||
|
<li>include guards</li>
|
||||||
|
<li>forward declarations,</li>
|
||||||
|
<li>types,</li>
|
||||||
|
<li>and function for ADTs in <code>ms_general.h</code>, <code>ms_cell.h</code>, <code>ms_board.c</code>, <code>ms_game.h</code>, and <code>ms_ui_utils.h</code>.</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>Types in template files may be marked as <code><type></code> within header and source files or may be omitted completely. Replace <code><type></code> with the correct type and complete missing types. Some types, those which are shared among multiple sources, are located in <code>general.h</code>.</li>
|
||||||
|
<li>Parameter lists of function in a template files may be missing or incomplete.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Make the program and tests compile: Implement all functions in all relevant files declared in the headers EMTPY (return nothing, 0, false, ... as required).<ul>
|
||||||
|
<li>All unit tests shall run but FAIL after this step</li>
|
||||||
|
<li><b>–COMMIT–</b></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Implement the empty functions one by one to make the unit tests pass one by one.<ul>
|
||||||
|
<li>Proposed order: ms_ui_utils, ms_cell, ms_board, ms_game.</li>
|
||||||
|
<li>The purpose of a function is specified as API documentation within the header files.</li>
|
||||||
|
<li>Obey comments in source files. Run the unit tests frequently and fix failures.</li>
|
||||||
|
<li><b>–COMMIT– after each implemented function.</b></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Implement the main loop and other missing parts in <code>main_driver.c</code> functions.<ul>
|
||||||
|
<li><b>–COMMIT–</b></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Run the game and enjoy</li>
|
||||||
|
</ol>
|
||||||
|
<h1><a class="anchor" id="notes"></a>
|
||||||
|
Notes</h1>
|
||||||
|
<ol type="1">
|
||||||
|
<li>make cleantest: This new make target for clearing the console, building, and running unit test is available.</li>
|
||||||
|
<li>The unit test for solving the game <code>test_msg_get_state__success</code> fails in rare cases even if the implementation is corret.</li>
|
||||||
|
<li>Visualization is implemented for Linux shell, it will not work on Windows.</li>
|
||||||
|
<li>Sometimes changes are not properly detected by incremental builds. If something very strange happens during compilation, try to run <code>make clean</code> followed by <code>make</code> to start a clean build. This approach is also recommended after everthing is done, because some compiler warning appears only in clean builds. </li>
|
||||||
|
</ol>
|
||||||
|
</div></div><!-- PageDoc -->
|
||||||
|
</div><!-- contents -->
|
||||||
|
<!-- start footer part -->
|
||||||
|
<hr class="footer"/><address class="footer"><small>
|
||||||
|
Generated on Sun Dec 27 2020 21:16:06 for Mine Sweeper by  <a href="http://www.doxygen.org/index.html">
|
||||||
|
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
||||||
|
</a> 1.8.18
|
||||||
|
</small></address>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
35
html/jquery.js
vendored
Normal file
79
html/mainpage_8h.html
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||||
|
<meta name="generator" content="Doxygen 1.8.18"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<title>Mine Sweeper: mainpage.h File Reference</title>
|
||||||
|
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||||
|
<script type="text/javascript" src="jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="dynsections.js"></script>
|
||||||
|
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||||
|
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||||
|
<script type="text/javascript" src="search/search.js"></script>
|
||||||
|
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="doxygen_extra.css" rel="stylesheet" type="text/css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||||
|
<div id="titlearea">
|
||||||
|
<table cellspacing="0" cellpadding="0">
|
||||||
|
<tbody>
|
||||||
|
<tr style="height: 56px;">
|
||||||
|
<td id="projectalign" style="padding-left: 0.5em;">
|
||||||
|
<div id="projectname">Mine Sweeper
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- end header part -->
|
||||||
|
<!-- Generated by Doxygen 1.8.18 -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||||
|
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||||
|
/* @license-end */
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="menudata.js"></script>
|
||||||
|
<script type="text/javascript" src="menu.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||||
|
$(function() {
|
||||||
|
initMenu('',true,false,'search.php','Search');
|
||||||
|
$(document).ready(function() { init_search(); });
|
||||||
|
});
|
||||||
|
/* @license-end */</script>
|
||||||
|
<div id="main-nav"></div>
|
||||||
|
<!-- window showing the filter options -->
|
||||||
|
<div id="MSearchSelectWindow"
|
||||||
|
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||||
|
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||||
|
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- iframe showing the search results (closed by default) -->
|
||||||
|
<div id="MSearchResultsWindow">
|
||||||
|
<iframe src="javascript:void(0)" frameborder="0"
|
||||||
|
name="MSearchResults" id="MSearchResults">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div><!-- top -->
|
||||||
|
<div class="header">
|
||||||
|
<div class="headertitle">
|
||||||
|
<div class="title">mainpage.h File Reference</div> </div>
|
||||||
|
</div><!--header-->
|
||||||
|
<div class="contents">
|
||||||
|
|
||||||
|
<p><a href="mainpage_8h_source.html">Go to the source code of this file.</a></p>
|
||||||
|
</div><!-- contents -->
|
||||||
|
<!-- start footer part -->
|
||||||
|
<hr class="footer"/><address class="footer"><small>
|
||||||
|
Generated on Sun Dec 27 2020 21:16:06 for Mine Sweeper by  <a href="http://www.doxygen.org/index.html">
|
||||||
|
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
||||||
|
</a> 1.8.18
|
||||||
|
</small></address>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
78
html/mainpage_8h_source.html
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||||
|
<meta name="generator" content="Doxygen 1.8.18"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<title>Mine Sweeper: mainpage.h Source File</title>
|
||||||
|
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||||
|
<script type="text/javascript" src="jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="dynsections.js"></script>
|
||||||
|
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||||
|
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||||
|
<script type="text/javascript" src="search/search.js"></script>
|
||||||
|
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="doxygen_extra.css" rel="stylesheet" type="text/css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||||
|
<div id="titlearea">
|
||||||
|
<table cellspacing="0" cellpadding="0">
|
||||||
|
<tbody>
|
||||||
|
<tr style="height: 56px;">
|
||||||
|
<td id="projectalign" style="padding-left: 0.5em;">
|
||||||
|
<div id="projectname">Mine Sweeper
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- end header part -->
|
||||||
|
<!-- Generated by Doxygen 1.8.18 -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||||
|
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||||
|
/* @license-end */
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="menudata.js"></script>
|
||||||
|
<script type="text/javascript" src="menu.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||||
|
$(function() {
|
||||||
|
initMenu('',true,false,'search.php','Search');
|
||||||
|
$(document).ready(function() { init_search(); });
|
||||||
|
});
|
||||||
|
/* @license-end */</script>
|
||||||
|
<div id="main-nav"></div>
|
||||||
|
</div><!-- top -->
|
||||||
|
<!-- window showing the filter options -->
|
||||||
|
<div id="MSearchSelectWindow"
|
||||||
|
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||||
|
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||||
|
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- iframe showing the search results (closed by default) -->
|
||||||
|
<div id="MSearchResultsWindow">
|
||||||
|
<iframe src="javascript:void(0)" frameborder="0"
|
||||||
|
name="MSearchResults" id="MSearchResults">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<div class="headertitle">
|
||||||
|
<div class="title">mainpage.h</div> </div>
|
||||||
|
</div><!--header-->
|
||||||
|
<div class="contents">
|
||||||
|
<a href="mainpage_8h.html">Go to the documentation of this file.</a><div class="fragment"><div class="line"><a name="l00001"></a><span class="lineno"> 1</span>  </div>
|
||||||
|
</div><!-- fragment --></div><!-- contents -->
|
||||||
|
<!-- start footer part -->
|
||||||
|
<hr class="footer"/><address class="footer"><small>
|
||||||
|
Generated on Sun Dec 27 2020 21:16:06 for Mine Sweeper by  <a href="http://www.doxygen.org/index.html">
|
||||||
|
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
||||||
|
</a> 1.8.18
|
||||||
|
</small></address>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
51
html/menu.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
@licstart The following is the entire license notice for the JavaScript code in this file.
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (C) 1997-2020 by Dimitri van Heesch
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||||
|
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||||
|
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||||
|
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
@licend The above is the entire license notice for the JavaScript code in this file
|
||||||
|
*/
|
||||||
|
function initMenu(relPath,searchEnabled,serverSide,searchPage,search) {
|
||||||
|
function makeTree(data,relPath) {
|
||||||
|
var result='';
|
||||||
|
if ('children' in data) {
|
||||||
|
result+='<ul>';
|
||||||
|
for (var i in data.children) {
|
||||||
|
result+='<li><a href="'+relPath+data.children[i].url+'">'+
|
||||||
|
data.children[i].text+'</a>'+
|
||||||
|
makeTree(data.children[i],relPath)+'</li>';
|
||||||
|
}
|
||||||
|
result+='</ul>';
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#main-nav').append(makeTree(menudata,relPath));
|
||||||
|
$('#main-nav').children(':first').addClass('sm sm-dox').attr('id','main-menu');
|
||||||
|
if (searchEnabled) {
|
||||||
|
if (serverSide) {
|
||||||
|
$('#main-menu').append('<li style="float:right"><div id="MSearchBox" class="MSearchBoxInactive"><div class="left"><form id="FSearchBox" action="'+relPath+searchPage+'" method="get"><img id="MSearchSelect" src="'+relPath+'search/mag.png" alt=""/><input type="text" id="MSearchField" name="query" value="'+search+'" size="20" accesskey="S" onfocus="searchBox.OnSearchFieldFocus(true)" onblur="searchBox.OnSearchFieldFocus(false)"></form></div><div class="right"></div></div></li>');
|
||||||
|
} else {
|
||||||
|
$('#main-menu').append('<li style="float:right"><div id="MSearchBox" class="MSearchBoxInactive"><span class="left"><img id="MSearchSelect" src="'+relPath+'search/mag_sel.png" onmouseover="return searchBox.OnSearchSelectShow()" onmouseout="return searchBox.OnSearchSelectHide()" alt=""/><input type="text" id="MSearchField" value="'+search+'" accesskey="S" onfocus="searchBox.OnSearchFieldFocus(true)" onblur="searchBox.OnSearchFieldFocus(false)" onkeyup="searchBox.OnSearchFieldChange(event)"/></span><span class="right"><a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="'+relPath+'search/close.png" alt=""/></a></span></div></li>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$('#main-menu').smartmenus();
|
||||||
|
}
|
||||||
|
/* @license-end */
|
||||||
28
html/menudata.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
@licstart The following is the entire license notice for the JavaScript code in this file.
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (C) 1997-2020 by Dimitri van Heesch
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||||
|
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||||
|
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||||
|
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
@licend The above is the entire license notice for the JavaScript code in this file
|
||||||
|
*/
|
||||||
|
var menudata={children:[
|
||||||
|
{text:"Main Page",url:"index.html"},
|
||||||
|
{text:"Files",url:"files.html",children:[
|
||||||
|
{text:"File List",url:"files.html"}]}]}
|
||||||
BIN
html/nav_f.png
Normal file
|
After Width: | Height: | Size: 153 B |
BIN
html/nav_g.png
Normal file
|
After Width: | Height: | Size: 95 B |
BIN
html/nav_h.png
Normal file
|
After Width: | Height: | Size: 98 B |
BIN
html/open.png
Normal file
|
After Width: | Height: | Size: 123 B |
36
html/search/all_0.html
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html><head><title></title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||||
|
<meta name="generator" content="Doxygen 1.8.18"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="search.css"/>
|
||||||
|
<script type="text/javascript" src="all_0.js"></script>
|
||||||
|
<script type="text/javascript" src="search.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="SRPage">
|
||||||
|
<div id="SRIndex">
|
||||||
|
<div class="SRStatus" id="Loading">Loading...</div>
|
||||||
|
<div id="SRResults"></div>
|
||||||
|
<script type="text/javascript"><!--
|
||||||
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||||
|
createResults();
|
||||||
|
/* @license-end */
|
||||||
|
--></script>
|
||||||
|
<div class="SRStatus" id="Searching">Searching...</div>
|
||||||
|
<div class="SRStatus" id="NoMatches">No Matches</div>
|
||||||
|
<script type="text/javascript"><!--
|
||||||
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||||
|
document.getElementById("Loading").style.display="none";
|
||||||
|
document.getElementById("NoMatches").style.display="none";
|
||||||
|
var searchResults = new SearchResults("searchResults");
|
||||||
|
searchResults.Search();
|
||||||
|
window.addEventListener("message", function(event) {
|
||||||
|
if (event.data == "take_focus") {
|
||||||
|
var elem = searchResults.NavNext(0);
|
||||||
|
if (elem) elem.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/* @license-end */
|
||||||
|
--></script>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
4
html/search/all_0.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
var searchData=
|
||||||
|
[
|
||||||
|
['mainpage_2eh_0',['mainpage.h',['../mainpage_8h.html',1,'']]]
|
||||||
|
];
|
||||||
BIN
html/search/close.png
Normal file
|
After Width: | Height: | Size: 273 B |
36
html/search/files_0.html
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html><head><title></title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||||
|
<meta name="generator" content="Doxygen 1.8.18"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="search.css"/>
|
||||||
|
<script type="text/javascript" src="files_0.js"></script>
|
||||||
|
<script type="text/javascript" src="search.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="SRPage">
|
||||||
|
<div id="SRIndex">
|
||||||
|
<div class="SRStatus" id="Loading">Loading...</div>
|
||||||
|
<div id="SRResults"></div>
|
||||||
|
<script type="text/javascript"><!--
|
||||||
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||||
|
createResults();
|
||||||
|
/* @license-end */
|
||||||
|
--></script>
|
||||||
|
<div class="SRStatus" id="Searching">Searching...</div>
|
||||||
|
<div class="SRStatus" id="NoMatches">No Matches</div>
|
||||||
|
<script type="text/javascript"><!--
|
||||||
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||||
|
document.getElementById("Loading").style.display="none";
|
||||||
|
document.getElementById("NoMatches").style.display="none";
|
||||||
|
var searchResults = new SearchResults("searchResults");
|
||||||
|
searchResults.Search();
|
||||||
|
window.addEventListener("message", function(event) {
|
||||||
|
if (event.data == "take_focus") {
|
||||||
|
var elem = searchResults.NavNext(0);
|
||||||
|
if (elem) elem.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/* @license-end */
|
||||||
|
--></script>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
4
html/search/files_0.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
var searchData=
|
||||||
|
[
|
||||||
|
['mainpage_2eh_1',['mainpage.h',['../mainpage_8h.html',1,'']]]
|
||||||
|
];
|
||||||
BIN
html/search/mag_sel.png
Normal file
|
After Width: | Height: | Size: 465 B |
12
html/search/nomatches.html
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html><head><title></title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="search.css"/>
|
||||||
|
<script type="text/javascript" src="search.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="SRPage">
|
||||||
|
<div id="SRIndex">
|
||||||
|
<div class="SRStatus" id="NoMatches">No Matches</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
271
html/search/search.css
Normal file
|
|
@ -0,0 +1,271 @@
|
||||||
|
/*---------------- Search Box */
|
||||||
|
|
||||||
|
#FSearchBox {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchBox {
|
||||||
|
white-space : nowrap;
|
||||||
|
float: none;
|
||||||
|
margin-top: 8px;
|
||||||
|
right: 0px;
|
||||||
|
width: 170px;
|
||||||
|
height: 24px;
|
||||||
|
z-index: 102;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchBox .left
|
||||||
|
{
|
||||||
|
display:block;
|
||||||
|
position:absolute;
|
||||||
|
left:10px;
|
||||||
|
width:20px;
|
||||||
|
height:19px;
|
||||||
|
background:url('search_l.png') no-repeat;
|
||||||
|
background-position:right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchSelect {
|
||||||
|
display:block;
|
||||||
|
position:absolute;
|
||||||
|
width:20px;
|
||||||
|
height:19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left #MSearchSelect {
|
||||||
|
left:4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right #MSearchSelect {
|
||||||
|
right:5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchField {
|
||||||
|
display:block;
|
||||||
|
position:absolute;
|
||||||
|
height:19px;
|
||||||
|
background:url('search_m.png') repeat-x;
|
||||||
|
border:none;
|
||||||
|
width:115px;
|
||||||
|
margin-left:20px;
|
||||||
|
padding-left:4px;
|
||||||
|
color: #909090;
|
||||||
|
outline: none;
|
||||||
|
font: 9pt Arial, Verdana, sans-serif;
|
||||||
|
-webkit-border-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#FSearchBox #MSearchField {
|
||||||
|
margin-left:15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchBox .right {
|
||||||
|
display:block;
|
||||||
|
position:absolute;
|
||||||
|
right:10px;
|
||||||
|
top:8px;
|
||||||
|
width:20px;
|
||||||
|
height:19px;
|
||||||
|
background:url('search_r.png') no-repeat;
|
||||||
|
background-position:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchClose {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
background : none;
|
||||||
|
border: none;
|
||||||
|
margin: 0px 4px 0px 0px;
|
||||||
|
padding: 0px 0px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left #MSearchClose {
|
||||||
|
left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right #MSearchClose {
|
||||||
|
right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MSearchBoxActive #MSearchField {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*---------------- Search filter selection */
|
||||||
|
|
||||||
|
#MSearchSelectWindow {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
left: 0; top: 0;
|
||||||
|
border: 1px solid #90A5CE;
|
||||||
|
background-color: #F9FAFC;
|
||||||
|
z-index: 10001;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-webkit-border-top-left-radius: 4px;
|
||||||
|
-webkit-border-top-right-radius: 4px;
|
||||||
|
-webkit-border-bottom-left-radius: 4px;
|
||||||
|
-webkit-border-bottom-right-radius: 4px;
|
||||||
|
-webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.SelectItem {
|
||||||
|
font: 8pt Arial, Verdana, sans-serif;
|
||||||
|
padding-left: 2px;
|
||||||
|
padding-right: 12px;
|
||||||
|
border: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SelectionMark {
|
||||||
|
margin-right: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
outline-style: none;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SelectItem {
|
||||||
|
display: block;
|
||||||
|
outline-style: none;
|
||||||
|
color: #000000;
|
||||||
|
text-decoration: none;
|
||||||
|
padding-left: 6px;
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SelectItem:focus,
|
||||||
|
a.SelectItem:active {
|
||||||
|
color: #000000;
|
||||||
|
outline-style: none;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SelectItem:hover {
|
||||||
|
color: #FFFFFF;
|
||||||
|
background-color: #3D578C;
|
||||||
|
outline-style: none;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*---------------- Search results window */
|
||||||
|
|
||||||
|
iframe#MSearchResults {
|
||||||
|
width: 60ex;
|
||||||
|
height: 15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchResultsWindow {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
left: 0; top: 0;
|
||||||
|
border: 1px solid #000;
|
||||||
|
background-color: #EEF1F7;
|
||||||
|
z-index:10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
#SRIndex {
|
||||||
|
clear:both;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SREntry {
|
||||||
|
font-size: 10pt;
|
||||||
|
padding-left: 1ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SRPage .SREntry {
|
||||||
|
font-size: 8pt;
|
||||||
|
padding: 1px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.SRPage {
|
||||||
|
margin: 5px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SRChildren {
|
||||||
|
padding-left: 3ex; padding-bottom: .5em
|
||||||
|
}
|
||||||
|
|
||||||
|
.SRPage .SRChildren {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SRSymbol {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #425E97;
|
||||||
|
font-family: Arial, Verdana, sans-serif;
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SRScope {
|
||||||
|
display: block;
|
||||||
|
color: #425E97;
|
||||||
|
font-family: Arial, Verdana, sans-serif;
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SRSymbol:focus, a.SRSymbol:active,
|
||||||
|
a.SRScope:focus, a.SRScope:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SRScope {
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SRPage .SRStatus {
|
||||||
|
padding: 2px 5px;
|
||||||
|
font-size: 8pt;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SRResult {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
DIV.searchresults {
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*---------------- External search page results */
|
||||||
|
|
||||||
|
.searchresult {
|
||||||
|
background-color: #F0F3F8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pages b {
|
||||||
|
color: white;
|
||||||
|
padding: 5px 5px 3px 5px;
|
||||||
|
background-image: url("../tab_a.png");
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
text-shadow: 0 1px 1px #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pages {
|
||||||
|
line-height: 17px;
|
||||||
|
margin-left: 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hl {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchresults {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchpages {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
814
html/search/search.js
Normal file
|
|
@ -0,0 +1,814 @@
|
||||||
|
/*
|
||||||
|
@licstart The following is the entire license notice for the JavaScript code in this file.
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (C) 1997-2020 by Dimitri van Heesch
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||||
|
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||||
|
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||||
|
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
@licend The above is the entire license notice for the JavaScript code in this file
|
||||||
|
*/
|
||||||
|
function convertToId(search)
|
||||||
|
{
|
||||||
|
var result = '';
|
||||||
|
for (i=0;i<search.length;i++)
|
||||||
|
{
|
||||||
|
var c = search.charAt(i);
|
||||||
|
var cn = c.charCodeAt(0);
|
||||||
|
if (c.match(/[a-z0-9\u0080-\uFFFF]/))
|
||||||
|
{
|
||||||
|
result+=c;
|
||||||
|
}
|
||||||
|
else if (cn<16)
|
||||||
|
{
|
||||||
|
result+="_0"+cn.toString(16);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result+="_"+cn.toString(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getXPos(item)
|
||||||
|
{
|
||||||
|
var x = 0;
|
||||||
|
if (item.offsetWidth)
|
||||||
|
{
|
||||||
|
while (item && item!=document.body)
|
||||||
|
{
|
||||||
|
x += item.offsetLeft;
|
||||||
|
item = item.offsetParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getYPos(item)
|
||||||
|
{
|
||||||
|
var y = 0;
|
||||||
|
if (item.offsetWidth)
|
||||||
|
{
|
||||||
|
while (item && item!=document.body)
|
||||||
|
{
|
||||||
|
y += item.offsetTop;
|
||||||
|
item = item.offsetParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A class handling everything associated with the search panel.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
name - The name of the global variable that will be
|
||||||
|
storing this instance. Is needed to be able to set timeouts.
|
||||||
|
resultPath - path to use for external files
|
||||||
|
*/
|
||||||
|
function SearchBox(name, resultsPath, inFrame, label)
|
||||||
|
{
|
||||||
|
if (!name || !resultsPath) { alert("Missing parameters to SearchBox."); }
|
||||||
|
|
||||||
|
// ---------- Instance variables
|
||||||
|
this.name = name;
|
||||||
|
this.resultsPath = resultsPath;
|
||||||
|
this.keyTimeout = 0;
|
||||||
|
this.keyTimeoutLength = 500;
|
||||||
|
this.closeSelectionTimeout = 300;
|
||||||
|
this.lastSearchValue = "";
|
||||||
|
this.lastResultsPage = "";
|
||||||
|
this.hideTimeout = 0;
|
||||||
|
this.searchIndex = 0;
|
||||||
|
this.searchActive = false;
|
||||||
|
this.insideFrame = inFrame;
|
||||||
|
this.searchLabel = label;
|
||||||
|
|
||||||
|
// ----------- DOM Elements
|
||||||
|
|
||||||
|
this.DOMSearchField = function()
|
||||||
|
{ return document.getElementById("MSearchField"); }
|
||||||
|
|
||||||
|
this.DOMSearchSelect = function()
|
||||||
|
{ return document.getElementById("MSearchSelect"); }
|
||||||
|
|
||||||
|
this.DOMSearchSelectWindow = function()
|
||||||
|
{ return document.getElementById("MSearchSelectWindow"); }
|
||||||
|
|
||||||
|
this.DOMPopupSearchResults = function()
|
||||||
|
{ return document.getElementById("MSearchResults"); }
|
||||||
|
|
||||||
|
this.DOMPopupSearchResultsWindow = function()
|
||||||
|
{ return document.getElementById("MSearchResultsWindow"); }
|
||||||
|
|
||||||
|
this.DOMSearchClose = function()
|
||||||
|
{ return document.getElementById("MSearchClose"); }
|
||||||
|
|
||||||
|
this.DOMSearchBox = function()
|
||||||
|
{ return document.getElementById("MSearchBox"); }
|
||||||
|
|
||||||
|
// ------------ Event Handlers
|
||||||
|
|
||||||
|
// Called when focus is added or removed from the search field.
|
||||||
|
this.OnSearchFieldFocus = function(isActive)
|
||||||
|
{
|
||||||
|
this.Activate(isActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.OnSearchSelectShow = function()
|
||||||
|
{
|
||||||
|
var searchSelectWindow = this.DOMSearchSelectWindow();
|
||||||
|
var searchField = this.DOMSearchSelect();
|
||||||
|
|
||||||
|
if (this.insideFrame)
|
||||||
|
{
|
||||||
|
var left = getXPos(searchField);
|
||||||
|
var top = getYPos(searchField);
|
||||||
|
left += searchField.offsetWidth + 6;
|
||||||
|
top += searchField.offsetHeight;
|
||||||
|
|
||||||
|
// show search selection popup
|
||||||
|
searchSelectWindow.style.display='block';
|
||||||
|
left -= searchSelectWindow.offsetWidth;
|
||||||
|
searchSelectWindow.style.left = left + 'px';
|
||||||
|
searchSelectWindow.style.top = top + 'px';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var left = getXPos(searchField);
|
||||||
|
var top = getYPos(searchField);
|
||||||
|
top += searchField.offsetHeight;
|
||||||
|
|
||||||
|
// show search selection popup
|
||||||
|
searchSelectWindow.style.display='block';
|
||||||
|
searchSelectWindow.style.left = left + 'px';
|
||||||
|
searchSelectWindow.style.top = top + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop selection hide timer
|
||||||
|
if (this.hideTimeout)
|
||||||
|
{
|
||||||
|
clearTimeout(this.hideTimeout);
|
||||||
|
this.hideTimeout=0;
|
||||||
|
}
|
||||||
|
return false; // to avoid "image drag" default event
|
||||||
|
}
|
||||||
|
|
||||||
|
this.OnSearchSelectHide = function()
|
||||||
|
{
|
||||||
|
this.hideTimeout = setTimeout(this.name +".CloseSelectionWindow()",
|
||||||
|
this.closeSelectionTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when the content of the search field is changed.
|
||||||
|
this.OnSearchFieldChange = function(evt)
|
||||||
|
{
|
||||||
|
if (this.keyTimeout) // kill running timer
|
||||||
|
{
|
||||||
|
clearTimeout(this.keyTimeout);
|
||||||
|
this.keyTimeout = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = (evt) ? evt : window.event; // for IE
|
||||||
|
if (e.keyCode==40 || e.keyCode==13)
|
||||||
|
{
|
||||||
|
if (e.shiftKey==1)
|
||||||
|
{
|
||||||
|
this.OnSearchSelectShow();
|
||||||
|
var win=this.DOMSearchSelectWindow();
|
||||||
|
for (i=0;i<win.childNodes.length;i++)
|
||||||
|
{
|
||||||
|
var child = win.childNodes[i]; // get span within a
|
||||||
|
if (child.className=='SelectItem')
|
||||||
|
{
|
||||||
|
child.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.frames.MSearchResults.postMessage("take_focus", "*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.keyCode==27) // Escape out of the search field
|
||||||
|
{
|
||||||
|
this.DOMSearchField().blur();
|
||||||
|
this.DOMPopupSearchResultsWindow().style.display = 'none';
|
||||||
|
this.DOMSearchClose().style.display = 'none';
|
||||||
|
this.lastSearchValue = '';
|
||||||
|
this.Activate(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// strip whitespaces
|
||||||
|
var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
|
||||||
|
|
||||||
|
if (searchValue != this.lastSearchValue) // search value has changed
|
||||||
|
{
|
||||||
|
if (searchValue != "") // non-empty search
|
||||||
|
{
|
||||||
|
// set timer for search update
|
||||||
|
this.keyTimeout = setTimeout(this.name + '.Search()',
|
||||||
|
this.keyTimeoutLength);
|
||||||
|
}
|
||||||
|
else // empty search field
|
||||||
|
{
|
||||||
|
this.DOMPopupSearchResultsWindow().style.display = 'none';
|
||||||
|
this.DOMSearchClose().style.display = 'none';
|
||||||
|
this.lastSearchValue = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.SelectItemCount = function(id)
|
||||||
|
{
|
||||||
|
var count=0;
|
||||||
|
var win=this.DOMSearchSelectWindow();
|
||||||
|
for (i=0;i<win.childNodes.length;i++)
|
||||||
|
{
|
||||||
|
var child = win.childNodes[i]; // get span within a
|
||||||
|
if (child.className=='SelectItem')
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.SelectItemSet = function(id)
|
||||||
|
{
|
||||||
|
var i,j=0;
|
||||||
|
var win=this.DOMSearchSelectWindow();
|
||||||
|
for (i=0;i<win.childNodes.length;i++)
|
||||||
|
{
|
||||||
|
var child = win.childNodes[i]; // get span within a
|
||||||
|
if (child.className=='SelectItem')
|
||||||
|
{
|
||||||
|
var node = child.firstChild;
|
||||||
|
if (j==id)
|
||||||
|
{
|
||||||
|
node.innerHTML='•';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node.innerHTML=' ';
|
||||||
|
}
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when an search filter selection is made.
|
||||||
|
// set item with index id as the active item
|
||||||
|
this.OnSelectItem = function(id)
|
||||||
|
{
|
||||||
|
this.searchIndex = id;
|
||||||
|
this.SelectItemSet(id);
|
||||||
|
var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
|
||||||
|
if (searchValue!="" && this.searchActive) // something was found -> do a search
|
||||||
|
{
|
||||||
|
this.Search();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.OnSearchSelectKey = function(evt)
|
||||||
|
{
|
||||||
|
var e = (evt) ? evt : window.event; // for IE
|
||||||
|
if (e.keyCode==40 && this.searchIndex<this.SelectItemCount()) // Down
|
||||||
|
{
|
||||||
|
this.searchIndex++;
|
||||||
|
this.OnSelectItem(this.searchIndex);
|
||||||
|
}
|
||||||
|
else if (e.keyCode==38 && this.searchIndex>0) // Up
|
||||||
|
{
|
||||||
|
this.searchIndex--;
|
||||||
|
this.OnSelectItem(this.searchIndex);
|
||||||
|
}
|
||||||
|
else if (e.keyCode==13 || e.keyCode==27)
|
||||||
|
{
|
||||||
|
this.OnSelectItem(this.searchIndex);
|
||||||
|
this.CloseSelectionWindow();
|
||||||
|
this.DOMSearchField().focus();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------- Actions
|
||||||
|
|
||||||
|
// Closes the results window.
|
||||||
|
this.CloseResultsWindow = function()
|
||||||
|
{
|
||||||
|
this.DOMPopupSearchResultsWindow().style.display = 'none';
|
||||||
|
this.DOMSearchClose().style.display = 'none';
|
||||||
|
this.Activate(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.CloseSelectionWindow = function()
|
||||||
|
{
|
||||||
|
this.DOMSearchSelectWindow().style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performs a search.
|
||||||
|
this.Search = function()
|
||||||
|
{
|
||||||
|
this.keyTimeout = 0;
|
||||||
|
|
||||||
|
// strip leading whitespace
|
||||||
|
var searchValue = this.DOMSearchField().value.replace(/^ +/, "");
|
||||||
|
|
||||||
|
var code = searchValue.toLowerCase().charCodeAt(0);
|
||||||
|
var idxChar = searchValue.substr(0, 1).toLowerCase();
|
||||||
|
if ( 0xD800 <= code && code <= 0xDBFF && searchValue > 1) // surrogate pair
|
||||||
|
{
|
||||||
|
idxChar = searchValue.substr(0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultsPage;
|
||||||
|
var resultsPageWithSearch;
|
||||||
|
var hasResultsPage;
|
||||||
|
|
||||||
|
var idx = indexSectionsWithContent[this.searchIndex].indexOf(idxChar);
|
||||||
|
if (idx!=-1)
|
||||||
|
{
|
||||||
|
var hexCode=idx.toString(16);
|
||||||
|
resultsPage = this.resultsPath + '/' + indexSectionNames[this.searchIndex] + '_' + hexCode + '.html';
|
||||||
|
resultsPageWithSearch = resultsPage+'?'+escape(searchValue);
|
||||||
|
hasResultsPage = true;
|
||||||
|
}
|
||||||
|
else // nothing available for this search term
|
||||||
|
{
|
||||||
|
resultsPage = this.resultsPath + '/nomatches.html';
|
||||||
|
resultsPageWithSearch = resultsPage;
|
||||||
|
hasResultsPage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.frames.MSearchResults.location = resultsPageWithSearch;
|
||||||
|
var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow();
|
||||||
|
|
||||||
|
if (domPopupSearchResultsWindow.style.display!='block')
|
||||||
|
{
|
||||||
|
var domSearchBox = this.DOMSearchBox();
|
||||||
|
this.DOMSearchClose().style.display = 'inline';
|
||||||
|
if (this.insideFrame)
|
||||||
|
{
|
||||||
|
var domPopupSearchResults = this.DOMPopupSearchResults();
|
||||||
|
domPopupSearchResultsWindow.style.position = 'relative';
|
||||||
|
domPopupSearchResultsWindow.style.display = 'block';
|
||||||
|
var width = document.body.clientWidth - 8; // the -8 is for IE :-(
|
||||||
|
domPopupSearchResultsWindow.style.width = width + 'px';
|
||||||
|
domPopupSearchResults.style.width = width + 'px';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var domPopupSearchResults = this.DOMPopupSearchResults();
|
||||||
|
var left = getXPos(domSearchBox) + 150; // domSearchBox.offsetWidth;
|
||||||
|
var top = getYPos(domSearchBox) + 20; // domSearchBox.offsetHeight + 1;
|
||||||
|
domPopupSearchResultsWindow.style.display = 'block';
|
||||||
|
left -= domPopupSearchResults.offsetWidth;
|
||||||
|
domPopupSearchResultsWindow.style.top = top + 'px';
|
||||||
|
domPopupSearchResultsWindow.style.left = left + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastSearchValue = searchValue;
|
||||||
|
this.lastResultsPage = resultsPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- Activation Functions
|
||||||
|
|
||||||
|
// Activates or deactivates the search panel, resetting things to
|
||||||
|
// their default values if necessary.
|
||||||
|
this.Activate = function(isActive)
|
||||||
|
{
|
||||||
|
if (isActive || // open it
|
||||||
|
this.DOMPopupSearchResultsWindow().style.display == 'block'
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.DOMSearchBox().className = 'MSearchBoxActive';
|
||||||
|
|
||||||
|
var searchField = this.DOMSearchField();
|
||||||
|
|
||||||
|
if (searchField.value == this.searchLabel) // clear "Search" term upon entry
|
||||||
|
{
|
||||||
|
searchField.value = '';
|
||||||
|
this.searchActive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!isActive) // directly remove the panel
|
||||||
|
{
|
||||||
|
this.DOMSearchBox().className = 'MSearchBoxInactive';
|
||||||
|
this.DOMSearchField().value = this.searchLabel;
|
||||||
|
this.searchActive = false;
|
||||||
|
this.lastSearchValue = ''
|
||||||
|
this.lastResultsPage = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// The class that handles everything on the search results page.
|
||||||
|
function SearchResults(name)
|
||||||
|
{
|
||||||
|
// The number of matches from the last run of <Search()>.
|
||||||
|
this.lastMatchCount = 0;
|
||||||
|
this.lastKey = 0;
|
||||||
|
this.repeatOn = false;
|
||||||
|
|
||||||
|
// Toggles the visibility of the passed element ID.
|
||||||
|
this.FindChildElement = function(id)
|
||||||
|
{
|
||||||
|
var parentElement = document.getElementById(id);
|
||||||
|
var element = parentElement.firstChild;
|
||||||
|
|
||||||
|
while (element && element!=parentElement)
|
||||||
|
{
|
||||||
|
if (element.nodeName == 'DIV' && element.className == 'SRChildren')
|
||||||
|
{
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.nodeName == 'DIV' && element.hasChildNodes())
|
||||||
|
{
|
||||||
|
element = element.firstChild;
|
||||||
|
}
|
||||||
|
else if (element.nextSibling)
|
||||||
|
{
|
||||||
|
element = element.nextSibling;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
element = element.parentNode;
|
||||||
|
}
|
||||||
|
while (element && element!=parentElement && !element.nextSibling);
|
||||||
|
|
||||||
|
if (element && element!=parentElement)
|
||||||
|
{
|
||||||
|
element = element.nextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Toggle = function(id)
|
||||||
|
{
|
||||||
|
var element = this.FindChildElement(id);
|
||||||
|
if (element)
|
||||||
|
{
|
||||||
|
if (element.style.display == 'block')
|
||||||
|
{
|
||||||
|
element.style.display = 'none';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
element.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Searches for the passed string. If there is no parameter,
|
||||||
|
// it takes it from the URL query.
|
||||||
|
//
|
||||||
|
// Always returns true, since other documents may try to call it
|
||||||
|
// and that may or may not be possible.
|
||||||
|
this.Search = function(search)
|
||||||
|
{
|
||||||
|
if (!search) // get search word from URL
|
||||||
|
{
|
||||||
|
search = window.location.search;
|
||||||
|
search = search.substring(1); // Remove the leading '?'
|
||||||
|
search = unescape(search);
|
||||||
|
}
|
||||||
|
|
||||||
|
search = search.replace(/^ +/, ""); // strip leading spaces
|
||||||
|
search = search.replace(/ +$/, ""); // strip trailing spaces
|
||||||
|
search = search.toLowerCase();
|
||||||
|
search = convertToId(search);
|
||||||
|
|
||||||
|
var resultRows = document.getElementsByTagName("div");
|
||||||
|
var matches = 0;
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
while (i < resultRows.length)
|
||||||
|
{
|
||||||
|
var row = resultRows.item(i);
|
||||||
|
if (row.className == "SRResult")
|
||||||
|
{
|
||||||
|
var rowMatchName = row.id.toLowerCase();
|
||||||
|
rowMatchName = rowMatchName.replace(/^sr\d*_/, ''); // strip 'sr123_'
|
||||||
|
|
||||||
|
if (search.length<=rowMatchName.length &&
|
||||||
|
rowMatchName.substr(0, search.length)==search)
|
||||||
|
{
|
||||||
|
row.style.display = 'block';
|
||||||
|
matches++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
row.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
document.getElementById("Searching").style.display='none';
|
||||||
|
if (matches == 0) // no results
|
||||||
|
{
|
||||||
|
document.getElementById("NoMatches").style.display='block';
|
||||||
|
}
|
||||||
|
else // at least one result
|
||||||
|
{
|
||||||
|
document.getElementById("NoMatches").style.display='none';
|
||||||
|
}
|
||||||
|
this.lastMatchCount = matches;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the first item with index index or higher that is visible
|
||||||
|
this.NavNext = function(index)
|
||||||
|
{
|
||||||
|
var focusItem;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
var focusName = 'Item'+index;
|
||||||
|
focusItem = document.getElementById(focusName);
|
||||||
|
if (focusItem && focusItem.parentNode.parentNode.style.display=='block')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (!focusItem) // last element
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
focusItem=null;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return focusItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.NavPrev = function(index)
|
||||||
|
{
|
||||||
|
var focusItem;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
var focusName = 'Item'+index;
|
||||||
|
focusItem = document.getElementById(focusName);
|
||||||
|
if (focusItem && focusItem.parentNode.parentNode.style.display=='block')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (!focusItem) // last element
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
focusItem=null;
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
return focusItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ProcessKeys = function(e)
|
||||||
|
{
|
||||||
|
if (e.type == "keydown")
|
||||||
|
{
|
||||||
|
this.repeatOn = false;
|
||||||
|
this.lastKey = e.keyCode;
|
||||||
|
}
|
||||||
|
else if (e.type == "keypress")
|
||||||
|
{
|
||||||
|
if (!this.repeatOn)
|
||||||
|
{
|
||||||
|
if (this.lastKey) this.repeatOn = true;
|
||||||
|
return false; // ignore first keypress after keydown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.type == "keyup")
|
||||||
|
{
|
||||||
|
this.lastKey = 0;
|
||||||
|
this.repeatOn = false;
|
||||||
|
}
|
||||||
|
return this.lastKey!=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Nav = function(evt,itemIndex)
|
||||||
|
{
|
||||||
|
var e = (evt) ? evt : window.event; // for IE
|
||||||
|
if (e.keyCode==13) return true;
|
||||||
|
if (!this.ProcessKeys(e)) return false;
|
||||||
|
|
||||||
|
if (this.lastKey==38) // Up
|
||||||
|
{
|
||||||
|
var newIndex = itemIndex-1;
|
||||||
|
var focusItem = this.NavPrev(newIndex);
|
||||||
|
if (focusItem)
|
||||||
|
{
|
||||||
|
var child = this.FindChildElement(focusItem.parentNode.parentNode.id);
|
||||||
|
if (child && child.style.display == 'block') // children visible
|
||||||
|
{
|
||||||
|
var n=0;
|
||||||
|
var tmpElem;
|
||||||
|
while (1) // search for last child
|
||||||
|
{
|
||||||
|
tmpElem = document.getElementById('Item'+newIndex+'_c'+n);
|
||||||
|
if (tmpElem)
|
||||||
|
{
|
||||||
|
focusItem = tmpElem;
|
||||||
|
}
|
||||||
|
else // found it!
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (focusItem)
|
||||||
|
{
|
||||||
|
focusItem.focus();
|
||||||
|
}
|
||||||
|
else // return focus to search field
|
||||||
|
{
|
||||||
|
parent.document.getElementById("MSearchField").focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.lastKey==40) // Down
|
||||||
|
{
|
||||||
|
var newIndex = itemIndex+1;
|
||||||
|
var focusItem;
|
||||||
|
var item = document.getElementById('Item'+itemIndex);
|
||||||
|
var elem = this.FindChildElement(item.parentNode.parentNode.id);
|
||||||
|
if (elem && elem.style.display == 'block') // children visible
|
||||||
|
{
|
||||||
|
focusItem = document.getElementById('Item'+itemIndex+'_c0');
|
||||||
|
}
|
||||||
|
if (!focusItem) focusItem = this.NavNext(newIndex);
|
||||||
|
if (focusItem) focusItem.focus();
|
||||||
|
}
|
||||||
|
else if (this.lastKey==39) // Right
|
||||||
|
{
|
||||||
|
var item = document.getElementById('Item'+itemIndex);
|
||||||
|
var elem = this.FindChildElement(item.parentNode.parentNode.id);
|
||||||
|
if (elem) elem.style.display = 'block';
|
||||||
|
}
|
||||||
|
else if (this.lastKey==37) // Left
|
||||||
|
{
|
||||||
|
var item = document.getElementById('Item'+itemIndex);
|
||||||
|
var elem = this.FindChildElement(item.parentNode.parentNode.id);
|
||||||
|
if (elem) elem.style.display = 'none';
|
||||||
|
}
|
||||||
|
else if (this.lastKey==27) // Escape
|
||||||
|
{
|
||||||
|
parent.searchBox.CloseResultsWindow();
|
||||||
|
parent.document.getElementById("MSearchField").focus();
|
||||||
|
}
|
||||||
|
else if (this.lastKey==13) // Enter
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.NavChild = function(evt,itemIndex,childIndex)
|
||||||
|
{
|
||||||
|
var e = (evt) ? evt : window.event; // for IE
|
||||||
|
if (e.keyCode==13) return true;
|
||||||
|
if (!this.ProcessKeys(e)) return false;
|
||||||
|
|
||||||
|
if (this.lastKey==38) // Up
|
||||||
|
{
|
||||||
|
if (childIndex>0)
|
||||||
|
{
|
||||||
|
var newIndex = childIndex-1;
|
||||||
|
document.getElementById('Item'+itemIndex+'_c'+newIndex).focus();
|
||||||
|
}
|
||||||
|
else // already at first child, jump to parent
|
||||||
|
{
|
||||||
|
document.getElementById('Item'+itemIndex).focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.lastKey==40) // Down
|
||||||
|
{
|
||||||
|
var newIndex = childIndex+1;
|
||||||
|
var elem = document.getElementById('Item'+itemIndex+'_c'+newIndex);
|
||||||
|
if (!elem) // last child, jump to parent next parent
|
||||||
|
{
|
||||||
|
elem = this.NavNext(itemIndex+1);
|
||||||
|
}
|
||||||
|
if (elem)
|
||||||
|
{
|
||||||
|
elem.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.lastKey==27) // Escape
|
||||||
|
{
|
||||||
|
parent.searchBox.CloseResultsWindow();
|
||||||
|
parent.document.getElementById("MSearchField").focus();
|
||||||
|
}
|
||||||
|
else if (this.lastKey==13) // Enter
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setKeyActions(elem,action)
|
||||||
|
{
|
||||||
|
elem.setAttribute('onkeydown',action);
|
||||||
|
elem.setAttribute('onkeypress',action);
|
||||||
|
elem.setAttribute('onkeyup',action);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setClassAttr(elem,attr)
|
||||||
|
{
|
||||||
|
elem.setAttribute('class',attr);
|
||||||
|
elem.setAttribute('className',attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createResults()
|
||||||
|
{
|
||||||
|
var results = document.getElementById("SRResults");
|
||||||
|
for (var e=0; e<searchData.length; e++)
|
||||||
|
{
|
||||||
|
var id = searchData[e][0];
|
||||||
|
var srResult = document.createElement('div');
|
||||||
|
srResult.setAttribute('id','SR_'+id);
|
||||||
|
setClassAttr(srResult,'SRResult');
|
||||||
|
var srEntry = document.createElement('div');
|
||||||
|
setClassAttr(srEntry,'SREntry');
|
||||||
|
var srLink = document.createElement('a');
|
||||||
|
srLink.setAttribute('id','Item'+e);
|
||||||
|
setKeyActions(srLink,'return searchResults.Nav(event,'+e+')');
|
||||||
|
setClassAttr(srLink,'SRSymbol');
|
||||||
|
srLink.innerHTML = searchData[e][1][0];
|
||||||
|
srEntry.appendChild(srLink);
|
||||||
|
if (searchData[e][1].length==2) // single result
|
||||||
|
{
|
||||||
|
srLink.setAttribute('href',searchData[e][1][1][0]);
|
||||||
|
if (searchData[e][1][1][1])
|
||||||
|
{
|
||||||
|
srLink.setAttribute('target','_parent');
|
||||||
|
}
|
||||||
|
var srScope = document.createElement('span');
|
||||||
|
setClassAttr(srScope,'SRScope');
|
||||||
|
srScope.innerHTML = searchData[e][1][1][2];
|
||||||
|
srEntry.appendChild(srScope);
|
||||||
|
}
|
||||||
|
else // multiple results
|
||||||
|
{
|
||||||
|
srLink.setAttribute('href','javascript:searchResults.Toggle("SR_'+id+'")');
|
||||||
|
var srChildren = document.createElement('div');
|
||||||
|
setClassAttr(srChildren,'SRChildren');
|
||||||
|
for (var c=0; c<searchData[e][1].length-1; c++)
|
||||||
|
{
|
||||||
|
var srChild = document.createElement('a');
|
||||||
|
srChild.setAttribute('id','Item'+e+'_c'+c);
|
||||||
|
setKeyActions(srChild,'return searchResults.NavChild(event,'+e+','+c+')');
|
||||||
|
setClassAttr(srChild,'SRScope');
|
||||||
|
srChild.setAttribute('href',searchData[e][1][c+1][0]);
|
||||||
|
if (searchData[e][1][c+1][1])
|
||||||
|
{
|
||||||
|
srChild.setAttribute('target','_parent');
|
||||||
|
}
|
||||||
|
srChild.innerHTML = searchData[e][1][c+1][2];
|
||||||
|
srChildren.appendChild(srChild);
|
||||||
|
}
|
||||||
|
srEntry.appendChild(srChildren);
|
||||||
|
}
|
||||||
|
srResult.appendChild(srEntry);
|
||||||
|
results.appendChild(srResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init_search()
|
||||||
|
{
|
||||||
|
var results = document.getElementById("MSearchSelectWindow");
|
||||||
|
for (var key in indexSectionLabels)
|
||||||
|
{
|
||||||
|
var link = document.createElement('a');
|
||||||
|
link.setAttribute('class','SelectItem');
|
||||||
|
link.setAttribute('onclick','searchBox.OnSelectItem('+key+')');
|
||||||
|
link.href='javascript:void(0)';
|
||||||
|
link.innerHTML='<span class="SelectionMark"> </span>'+indexSectionLabels[key];
|
||||||
|
results.appendChild(link);
|
||||||
|
}
|
||||||
|
searchBox.OnSelectItem(0);
|
||||||
|
}
|
||||||
|
/* @license-end */
|
||||||
BIN
html/search/search_l.png
Normal file
|
After Width: | Height: | Size: 567 B |
BIN
html/search/search_m.png
Normal file
|
After Width: | Height: | Size: 158 B |
BIN
html/search/search_r.png
Normal file
|
After Width: | Height: | Size: 553 B |
18
html/search/searchdata.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
var indexSectionsWithContent =
|
||||||
|
{
|
||||||
|
0: "m",
|
||||||
|
1: "m"
|
||||||
|
};
|
||||||
|
|
||||||
|
var indexSectionNames =
|
||||||
|
{
|
||||||
|
0: "all",
|
||||||
|
1: "files"
|
||||||
|
};
|
||||||
|
|
||||||
|
var indexSectionLabels =
|
||||||
|
{
|
||||||
|
0: "All",
|
||||||
|
1: "Files"
|
||||||
|
};
|
||||||
|
|
||||||
BIN
html/splitbar.png
Normal file
|
After Width: | Height: | Size: 314 B |
BIN
html/sync_off.png
Normal file
|
After Width: | Height: | Size: 853 B |
BIN
html/sync_on.png
Normal file
|
After Width: | Height: | Size: 845 B |
BIN
html/tab_a.png
Normal file
|
After Width: | Height: | Size: 142 B |
BIN
html/tab_b.png
Normal file
|
After Width: | Height: | Size: 169 B |
BIN
html/tab_h.png
Normal file
|
After Width: | Height: | Size: 177 B |
BIN
html/tab_s.png
Normal file
|
After Width: | Height: | Size: 184 B |
1
html/tabs.css
Normal file
1
index.html
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
html/index.html
|
||||||
101
mainpage.h
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
/**
|
||||||
|
* @mainpage
|
||||||
|
* @section intro Introduction
|
||||||
|
* The console version of the popular 'Mine Sweeper' game.
|
||||||
|
*
|
||||||
|
* The board consists of initially covered cells. Some of these cells carries mines.
|
||||||
|
*
|
||||||
|
* Cells can be marked as having a mine (mine detected) or as being suspected of carrying a mine (mine suspected).
|
||||||
|
*
|
||||||
|
* Cells can be uncovered to reveal their content.
|
||||||
|
* Cells may show a number after they are uncovered. This number tells how many of their eight direct neighbor cells
|
||||||
|
* carries a mine and are therefore 'dangerous neighbors'. If a cell without dangerous neighbors is uncovered,
|
||||||
|
* all neighbor cells of that cell are uncovered as well. If such a neighbor cell again has no dangerous neighbors,
|
||||||
|
* all neighbor cells of this cell are uncovered also, and so on.
|
||||||
|
*
|
||||||
|
* The objective of the game is find all cells carrying a mine without uncovering those. The game is won,
|
||||||
|
* if each cell carrying a mine is marked as such (mine detected) AND all other cells are uncovered.
|
||||||
|
* If a cell that carries a mine is uncovered, the game is last as the mine detonates.
|
||||||
|
*
|
||||||
|
* Cells are addressed by their column and row coordinate. Before any action can be taken on a cell, it
|
||||||
|
* need to be selected first. Afterwards actions such as setting or clearing markers as well as uncovering
|
||||||
|
* the cell can be performed. Alternatively a different cell can be selected. This approach shall avoid
|
||||||
|
* performing an action on a 'wrong' cell.
|
||||||
|
*
|
||||||
|
* @section objective Assignment Objective
|
||||||
|
* In this assignment the required Abstract Data Types and the game logic as well as user interface interaction
|
||||||
|
* shall be implemented. A visualizer for the complete game is provided and shall be used. The visualizer is also
|
||||||
|
* capable of capturing user inputs and provides the captured result.
|
||||||
|
*
|
||||||
|
* The 'main loop' of the application shall be implemented in `ms_main_driver.c`, function `ui_branch`.
|
||||||
|
*
|
||||||
|
* That main loop shall:
|
||||||
|
* #Start a game
|
||||||
|
* #visualize the initial game using `ms_visualizer`
|
||||||
|
* #evaluate the user input provided after `ms_visualize()` returns
|
||||||
|
* #perform the desired action, e.g. selecting a cell, set a marker, or quit the game
|
||||||
|
* #continue with step 2 until the captured action is 'quit' or the game is over (solved or failed)
|
||||||
|
*
|
||||||
|
* The minimal main loop described above may be extended (surrounded) by an outer loop, which allows to
|
||||||
|
* restart a game in a different mode.
|
||||||
|
*
|
||||||
|
* Note that `ms_visualizer` supported a 'cheat' mode, which shows the content of covered cells. This may
|
||||||
|
* be helpful during development.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* **Configuration**
|
||||||
|
*
|
||||||
|
* Because we operate on static memory, the maximum size of the board in each dimension can be configured in `config.h`.
|
||||||
|
*
|
||||||
|
* **Setup and Execution**
|
||||||
|
*
|
||||||
|
* Initially the board has the set number of cells, all cells are covered. No cell carries a mine at this point in time.
|
||||||
|
* After the user uncovers the first cell, the mines are distributes and the number of dangerous neighbors is calculated.
|
||||||
|
* The first uncovered cell shall be empty (and therefore uncover neighbor cells, as described above) for sure, to
|
||||||
|
* avoid ambiguous configuration on the first action.
|
||||||
|
*
|
||||||
|
* **Visualization**
|
||||||
|
|
||||||
|
* As described above, the visualization library realized by `ms_visualizer` is capable to render the complete game, including
|
||||||
|
* status bar and user prompts as well as capturing user input. User interface related texts and symbols can be more or less
|
||||||
|
* freely defined in `ms_ui_utils.c`. The visualizer does not make assumptions on marker presentation or input keys.
|
||||||
|
*
|
||||||
|
* @section assignment Assignment
|
||||||
|
* This is a rather complex assignment, therefore it is vital, to develop the code in a focused and clean way, step by step.
|
||||||
|
* Many unit tests are provided to ease development. Some unit tests provide also helpful debugging traces. If too many
|
||||||
|
* failed unit tests appear - especially at the beginning of development- they can be commented in file `ms_test_driver.c`
|
||||||
|
* for files that are currently not in focus. However, they need to be reactivated as development proceeds.
|
||||||
|
*
|
||||||
|
* Hint: Read and obey the comment within the code.
|
||||||
|
*
|
||||||
|
* -# Complete
|
||||||
|
* - include guards
|
||||||
|
* - forward declarations,
|
||||||
|
* - types,
|
||||||
|
* - and function for ADTs in `ms_general.h`, `ms_cell.h`, `ms_board.c`, `ms_game.h`, and `ms_ui_utils.h`.
|
||||||
|
* .
|
||||||
|
* - Types in template files may be marked as `<type>` within header and source files or may be omitted completely. Replace `<type>`
|
||||||
|
* with the correct type and complete missing types. Some types, those which are shared among multiple sources, are located in `general.h`.
|
||||||
|
* - Parameter lists of function in a template files may be missing or incomplete.
|
||||||
|
* -# Make the program and tests compile: Implement all functions in all relevant files declared in the headers
|
||||||
|
* EMTPY (return nothing, 0, false, ... as required).
|
||||||
|
* - All unit tests shall run but FAIL after this step
|
||||||
|
* - **--COMMIT--**
|
||||||
|
* -# Implement the empty functions one by one to make the unit tests pass one by one.
|
||||||
|
* - Proposed order: ms_ui_utils, ms_cell, ms_board, ms_game.
|
||||||
|
* - The purpose of a function is specified as API documentation within the header files.
|
||||||
|
* - Obey comments in source files. Run the unit tests frequently and fix failures.
|
||||||
|
* - **--COMMIT-- after each implemented function.**
|
||||||
|
* -# Implement the main loop and other missing parts in `main_driver.c` functions.
|
||||||
|
* - **--COMMIT--**
|
||||||
|
* -# Run the game and enjoy
|
||||||
|
*
|
||||||
|
* @section notes Notes
|
||||||
|
* -# make cleantest: This new make target for clearing the console, building, and running unit test is available.
|
||||||
|
* -# The unit test for solving the game `test_msg_get_state__success` fails in rare cases even if the implementation is corret.
|
||||||
|
* -# Visualization is implemented for Linux shell, it will not work on Windows.
|
||||||
|
* -# Sometimes changes are not properly detected by incremental builds. If something very strange
|
||||||
|
* happens during compilation, try to run `make clean` followed by `make` to start a clean build.
|
||||||
|
* This approach is also recommended after everthing is done, because some compiler warning appears
|
||||||
|
* only in clean builds.
|
||||||
|
*/
|
||||||
98
makefile
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
CC = gcc
|
||||||
|
CCLINK = g++
|
||||||
|
LIBS =
|
||||||
|
CCOPTIONS = -Wall -pedantic -std=gnu17 -g
|
||||||
|
LDOPTIONS =
|
||||||
|
|
||||||
|
BUILD_DIR = build
|
||||||
|
|
||||||
|
TEST = test_minesweeper
|
||||||
|
PROGRAM = minesweeper
|
||||||
|
|
||||||
|
COMMON_HDRS = config.h
|
||||||
|
LIBRARY_FILES = ms_visualizer shortcut
|
||||||
|
ASSIGNMENT_HDRS = general.h
|
||||||
|
ASSIGNMENT_FILES = ms_board ms_cell ms_game ms_ui_utils
|
||||||
|
MAIN_DRIVER = ms_main_driver
|
||||||
|
TEST_DRIVER = ms_test_driver
|
||||||
|
|
||||||
|
LIBRARY_H = $(addsuffix .h, $(LIBRARY_FILES))
|
||||||
|
ASSIGNMENT_H = $(addsuffix .h, $(ASSIGNMENT_FILES)) $(ASSIGNMENT_HDRS)
|
||||||
|
ASSIGNMENT_C = $(addsuffix .c, $(ASSIGNMENT_FILES)) $(MAIN_DRIVER).c
|
||||||
|
|
||||||
|
HDRS = $(ASSIGNEMT_H) $(SHARED_HDRS) $(COMMON_HDRS) $(LIBRARY_H)
|
||||||
|
TESTOBJECT = $(addprefix $(BUILD_DIR)/, $(TEST_DRIVER).o)
|
||||||
|
MAINOBJECT = $(addprefix $(BUILD_DIR)/, $(MAIN_DRIVER).o)
|
||||||
|
LIBRARY_OBJS = $(addprefix $(BUILD_DIR)/, $(addsuffix .o, $(LIBRARY_FILES)))
|
||||||
|
TEST_OBJS = $(addprefix $(BUILD_DIR)/, $(addprefix test_, $(addsuffix .o, $(ASSIGNMENT_FILES))))
|
||||||
|
MAIN_OBJ = $(addprefix $(BUILD_DIR)/, $(addsuffix .o, $(ASSIGNMENT_FILES)))
|
||||||
|
OBJS = $(LIBRARY_OBJS) $(MAIN_OBJ) $(TEST_OBJS)
|
||||||
|
|
||||||
|
DOXY = doxygen
|
||||||
|
|
||||||
|
all: $(PROGRAM)
|
||||||
|
./$(PROGRAM)
|
||||||
|
|
||||||
|
$(TEST): $(BUILD_DIR) $(OBJS) $(TESTOBJECT)
|
||||||
|
$(CCLINK) -o $@ $(LDOPTIONS) $(OBJS) $(TESTOBJECT)
|
||||||
|
|
||||||
|
$(PROGRAM): $(BUILD_DIR) $(OBJS) $(MAINOBJECT)
|
||||||
|
$(CCLINK) -o $@ $(LDOPTIONS) $(OBJS) $(MAINOBJECT)
|
||||||
|
|
||||||
|
.PHONY: clean cleanall doxy test setsample setassignment definesample defineassignment assignmentfolder
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(PROGRAM) $(TEST) $(TESTOBJECT) $(MAINOBJECT) $(OBJS)
|
||||||
|
rm -rf $(BUILD_DIR)
|
||||||
|
rm -f *.o
|
||||||
|
|
||||||
|
cleanall: clean
|
||||||
|
rm -f index.html
|
||||||
|
rm -rf html
|
||||||
|
|
||||||
|
|
||||||
|
doxy:
|
||||||
|
$(DOXY)
|
||||||
|
rm -f index.html
|
||||||
|
ln -s html/index.html index.html
|
||||||
|
|
||||||
|
test: $(TEST)
|
||||||
|
./$(TEST)
|
||||||
|
|
||||||
|
cleantest: clean
|
||||||
|
clear
|
||||||
|
make test
|
||||||
|
|
||||||
|
$(BUILD_DIR):
|
||||||
|
mkdir -p $(BUILD_DIR)
|
||||||
|
|
||||||
|
$(BUILD_DIR)/%.o: %.c
|
||||||
|
$(CC) $(CCOPTIONS) -c -o $@ $<
|
||||||
|
|
||||||
|
#sets project as sample solution
|
||||||
|
setsample:
|
||||||
|
$(foreach name, $(ASSIGNMENT_H) $(ASSIGNMENT_C), cp $(name).sample $(name);)
|
||||||
|
|
||||||
|
#sets project as assignment
|
||||||
|
setassignment:
|
||||||
|
$(foreach name, $(ASSIGNMENT_H) $(ASSIGNMENT_C), cp $(name).assignment $(name);)
|
||||||
|
|
||||||
|
# defines current state of project as sample solution
|
||||||
|
definesample:
|
||||||
|
$(foreach name, $(ASSIGNMENT_H) $(ASSIGNMENT_C), cp $(name) $(name).sample;)
|
||||||
|
|
||||||
|
# defines current sate of project as assignment
|
||||||
|
defineassignment :
|
||||||
|
$(foreach name, $(ASSIGNMENT_H) $(ASSIGNMENT_C), cp $(name) $(name).assignment;)
|
||||||
|
|
||||||
|
# creates a folder which can serve as a publishable assignment
|
||||||
|
assignmentfolder:
|
||||||
|
make setassignment
|
||||||
|
make doxy
|
||||||
|
rm -rf ../assignment
|
||||||
|
mkdir ../assignment
|
||||||
|
cp -R * ../assignment
|
||||||
|
cp .gitignore ../assignment
|
||||||
|
rm ../assignment/*.sample
|
||||||
|
rm ../assignment/*.assignment
|
||||||
|
make cleanall
|
||||||
19
ms_board.c
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Class: <your class>
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Mine Sweeper Board implementation
|
||||||
|
* Author: */<your name>/*
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Implementation of ms_board.h.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ms_board.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
/** Implementation of Mine Sweeper board data */
|
||||||
|
|
||||||
|
/** The private singleton instance of the board data. */
|
||||||
73
ms_board.h
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Class: <your class>
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Mine Sweeper
|
||||||
|
* Author: */<your name>/*
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* The declaration of an Abstract Data Type representing
|
||||||
|
* a game board for Mine Sweeper.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** MsBoard: Declares type for the 'Mine Sweeper' board */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the instance of the 'Mine Sweeper' board.
|
||||||
|
* Exactly one instance is supported. The board state
|
||||||
|
* is not affected by this function.
|
||||||
|
*
|
||||||
|
* @return The board instance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the only Mine Sweeper board:
|
||||||
|
* + Defines the actual size of the board (columns and rows) up to the configured values.
|
||||||
|
* + Initializes all cells as 'empty' and 'covered'.
|
||||||
|
* The number of columns and rows shall be clipped to MAX_BOARD_SIZE.
|
||||||
|
*
|
||||||
|
* @param board The board instance in focus.
|
||||||
|
* @param column_count The number of cell columns of the board.
|
||||||
|
* @param row_count The number of cell row of the board.
|
||||||
|
*/
|
||||||
|
<type> msb_init_board(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not the given board is valid.
|
||||||
|
* A board is NOT valid
|
||||||
|
* + if it is 0,
|
||||||
|
* + or if it has a size of 0 (0 columns or 0 rows).
|
||||||
|
*
|
||||||
|
* @param board The board instance in focus.
|
||||||
|
* @return True if the given board is valid, false otherwise.
|
||||||
|
*/
|
||||||
|
<type> msb_is_valid(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the cell at the given coordinates.
|
||||||
|
*
|
||||||
|
* @param board The board instance in focus.
|
||||||
|
* @param col_idx The index of the column of the addressed cell.
|
||||||
|
* @param row_idx The index of the row of the addressed cell.
|
||||||
|
* @return The addressed cell or 0, if the coordinates are not in range.
|
||||||
|
*/
|
||||||
|
<type> msb_get_cell(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the number of columns used by the current game (of the 'active' area).
|
||||||
|
*
|
||||||
|
* @param board The board instance in focus.
|
||||||
|
* @return The number of columns used for the current board configuration or 0,
|
||||||
|
* if the board is not configured or invalid.
|
||||||
|
*/
|
||||||
|
<type> msb_get_column_count(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the number of rows used by the current game (of the 'active' area).
|
||||||
|
*
|
||||||
|
* @param board The board instance in focus.
|
||||||
|
* @return The number of columns used for the current board configuration or 0,
|
||||||
|
* if the board is not configured or invalid.
|
||||||
|
*/
|
||||||
|
<type> msb_get_row_count(<params>);
|
||||||
19
ms_cell.c
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Class: <your class>
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Mine Sweeper Cell implementation
|
||||||
|
* Author: */<your name>/*
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Implementation of ms_cell.h.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define CELL_COUNT MAX_BOARD_SIZE * MAX_BOARD_SIZE
|
||||||
|
|
||||||
|
/** Implementation of cell data */
|
||||||
|
|
||||||
|
/** Maximum amount of cell instances */
|
||||||
|
|
||||||
|
/** The number of produced cells */
|
||||||
116
ms_cell.h
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Class: <your class>
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Mine Sweeper
|
||||||
|
* Author: */<your name>/*
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* The declaration of an Abstract Data Type representing
|
||||||
|
* a single cell of the Mine Sweeper board.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Note: The 'CellMarker' enumeration is declared in 'general.h'
|
||||||
|
to enable mapping between enum and presentation in 'ms_ui_utils' */
|
||||||
|
|
||||||
|
/** Declares type for a single 'Mine Sweeper' cell */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides another, yet unused instance, initialized Mine Sweeper cell
|
||||||
|
* The provided cell is covered, carries no marker, no mine, and has 0 dangerous neighbors.
|
||||||
|
*
|
||||||
|
* Up to `MAX_BOARD_SIZE * MAX_BOARD_SIZE` cells can be produced by subsequent
|
||||||
|
* calls of this function.
|
||||||
|
*
|
||||||
|
* @return The fresh cell instance or 0, if no more instance is available.
|
||||||
|
*/
|
||||||
|
<type> msc_produce_cell();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the internal cell factory. Function `msc_produce_cell` is capable
|
||||||
|
* to produce the full number of cell after this function is called.
|
||||||
|
*/
|
||||||
|
<type> msc_reset_cell_factory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not the given cell is valid.
|
||||||
|
* A cell is NOT valid, if it is 0 or has a neighbor mine count
|
||||||
|
* greater than 8.
|
||||||
|
*
|
||||||
|
* @param cell The cell instance in focus.
|
||||||
|
* @return True if the given cell is valid, false otherwise.
|
||||||
|
*/
|
||||||
|
<type> msc_is_valid(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not the given cell is currently covered.
|
||||||
|
*
|
||||||
|
* @param cell The cell instance in focus.
|
||||||
|
* @return True if the given cell is covered, false otherwise.
|
||||||
|
*/
|
||||||
|
<type> msc_is_covered(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uncovers the given cell.
|
||||||
|
* If the cell carries a veritable marker (detected or suspected)
|
||||||
|
* or is already uncovered, the request is ignored. Otherwise
|
||||||
|
* function `msc_is_covered` returns `false` for this cell afterwards.
|
||||||
|
*
|
||||||
|
* Note: an uncovered cell cannot be covered again.
|
||||||
|
*
|
||||||
|
* @param cell The cell instance in focus.
|
||||||
|
*/
|
||||||
|
<type> msc_uncover(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not the given cell carries a mine.
|
||||||
|
*
|
||||||
|
* @param cell The cell instance in focus.
|
||||||
|
* @return True if the cell carries a mine, false otherwise.
|
||||||
|
*/
|
||||||
|
<type> msc_has_mine(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies that the given cell carries a mine.
|
||||||
|
* Invocations for invalid cells are ignored.
|
||||||
|
*
|
||||||
|
* @param cell The cell instance in focus.
|
||||||
|
*/
|
||||||
|
<type> msc_drop_mine(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the number of direct neighbor cells, which carries a mine.
|
||||||
|
*
|
||||||
|
* @param cell The cell instance in focus.
|
||||||
|
* @return The number between 0 to 8 that encounters the neighbor cells
|
||||||
|
* that carries a mine or 255 if the cell is not valid.
|
||||||
|
*/
|
||||||
|
<type> msc_get_dangerous_neighbor_count(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments (+1) the count of neighbor cells that carries a mine.
|
||||||
|
* Invocations for invalid cells are ignored.
|
||||||
|
*
|
||||||
|
* @param cell The cell instance in focus.
|
||||||
|
*/
|
||||||
|
<type> msc_inc_dangerous_neighbor_count(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the given marker of the given cell.
|
||||||
|
*
|
||||||
|
* @param cell The cell instance in focus.
|
||||||
|
* @return The marker carried by the cell
|
||||||
|
* or `NONE`, if the cell is not valid.
|
||||||
|
*/
|
||||||
|
<type> msc_get_marker(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the given marker to the given cell. The marker is cleared
|
||||||
|
* by applying a 'none' marker.
|
||||||
|
* Invocations for invalid cells are ignored.
|
||||||
|
*
|
||||||
|
* @param cell The cell instance in focus.
|
||||||
|
* @param marker The marker to apply.
|
||||||
|
*/
|
||||||
|
<type> msc_set_marker(<params>);
|
||||||
20
ms_game.c
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Class: <your class>
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Mine Sweeper GAme implementation
|
||||||
|
* Author: */<your name>/*
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Implementation of ms_game.h.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/** Encapsulate a cell and its board coordinates */
|
||||||
|
|
||||||
|
/** The only instance of cell data */
|
||||||
|
|
||||||
|
/** The struct holding the data of the game */
|
||||||
|
|
||||||
|
/** The instance of the game */
|
||||||
165
ms_game.h
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Class: <your class>
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Mine Sweeper
|
||||||
|
* Author: */<your name>/*
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* The declaration of an Abstract Data Type representing
|
||||||
|
* the Mine Sweeper game.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** The type for the Mine Sweeper game. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a new game using the given mode. If a mode with a
|
||||||
|
* predefined configuration is used, this function shall actually start
|
||||||
|
* the game with that configuration using function `msg_start_game`.
|
||||||
|
* Otherwise, the user shall be prompted for required attributes
|
||||||
|
* (board size, mines) and the game is started afterwards.
|
||||||
|
*
|
||||||
|
* @param mode The configuration mode of the game to start.
|
||||||
|
* @return The game if if could be started with a predefined configuration,
|
||||||
|
* or 0 if it was not started because a custom configuration is required.
|
||||||
|
*/
|
||||||
|
<type> msg_start_configured_game(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a new game, regardless of the state of the current game.
|
||||||
|
* Therefore, completed games or games in progress are aborted
|
||||||
|
* and a new game is 'restarted'.
|
||||||
|
*
|
||||||
|
* Note: Mines are distributed when the first cell is uncovered
|
||||||
|
* to ensure to uncover an empty cell.
|
||||||
|
*
|
||||||
|
* @param column_count The number of columns to use on the game board.
|
||||||
|
* @param row_count The number of rows to use on the game board.
|
||||||
|
* @param mine_count The number of mines distributed on the board.
|
||||||
|
* It must be greater than 0;
|
||||||
|
* @return The started game or 0 in case errors.
|
||||||
|
*/
|
||||||
|
<type> msg_start_game(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not the given game is valid.
|
||||||
|
* A game is NOT valid, if:
|
||||||
|
* + it is 0
|
||||||
|
* + or if it has an invalid board
|
||||||
|
* + or if it has no mines
|
||||||
|
* + or if it has less than 10 cells
|
||||||
|
* + or if it has more mines than cells - 9.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @return True if the given game is valid, false otherwise.
|
||||||
|
*/
|
||||||
|
<type> msg_is_valid(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the addressed cell without performing any action on that cell. Only
|
||||||
|
* covered cells can be selected. The function clears any previous selection
|
||||||
|
* and selects the addressed cell. Therefore invalid addresses or already uncovered cells
|
||||||
|
* has only the effect of clearing a previous selection.
|
||||||
|
*
|
||||||
|
* To avoid uncovering unwanted cells due to wrong user inputs, the cell to uncover
|
||||||
|
* is selected in the first step. Only the selected cell can be uncovered.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @param column The column of the cell to select in the format used on the UI (e.g. 'A', 'B').
|
||||||
|
* @param row The row of the cell to select in the format used on the UI (e.g. 1, 2).
|
||||||
|
* @return True if the addressed cell is selected, false otherwise.
|
||||||
|
*/
|
||||||
|
<type> msg_select_cell(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the currently selected cell.
|
||||||
|
*
|
||||||
|
* @return The currently selected cell or 0,
|
||||||
|
* if no cell is selected.
|
||||||
|
*/
|
||||||
|
<type> msg_get_selected_cell(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uncovers the currently selected cell.
|
||||||
|
* To avoid uncovering unwanted cells due to wrong user inputs, the cell to uncover
|
||||||
|
* must be selected in the first step via function ´msg_select_cell´.
|
||||||
|
*
|
||||||
|
* Only the selected cell can be uncovered, otherwise the invocation is ignored.
|
||||||
|
* No cell is selected afterwards.
|
||||||
|
*
|
||||||
|
* Mines are randomly distributed on the board when the first cell of a game is uncovered.
|
||||||
|
* Mines shall be distributed in a way that keeps the first uncovered cell empty.
|
||||||
|
* Depending on the size of the board and the number of mines, the randomized mine distribution
|
||||||
|
* may take long to find a proper cell for all mines. Therefore mine distribution shall be
|
||||||
|
* aborted after 10 X mine-count trials. The number of mines on the board need to be updated
|
||||||
|
* accordingly, if not all mines could be dropped.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
*/
|
||||||
|
<type> msg_uncover_selected_cell(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the marker on the addressed cell. To clear the marker, the marker ´NONE´ is used.
|
||||||
|
* The cell to mark must be selected in the first step via function ´msg_select_cell´.
|
||||||
|
* Only the selected cell can be marked, otherwise the invocation is ignored.
|
||||||
|
* No cell is selected afterwards.
|
||||||
|
*
|
||||||
|
* Note: The number of set mine-detected markers must NOT be larger than the number
|
||||||
|
* of mines on the board.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @param marker The marker to apply.
|
||||||
|
*/
|
||||||
|
<type> msg_mark_selected_cell(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the state of the current game.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @return The state of the current game.
|
||||||
|
*/
|
||||||
|
<type> msg_get_state(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the number of mines that are detected.
|
||||||
|
* This is the difference of total number of mines the board carries
|
||||||
|
* minus the number of cells being marked as 'detected'.
|
||||||
|
* Cells marked as 'suspected' does not contribute to this value.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @return The number of undetected mines
|
||||||
|
* or 0, if the game is not valid.
|
||||||
|
*/
|
||||||
|
<type> msg_get_mines_left_count(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the number of cells that are suspected carrying mines.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @return The number of suspected mines
|
||||||
|
* or 0, if the game is not valid.
|
||||||
|
*/
|
||||||
|
<type> msg_get_mines_suspected_count(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the number of times a cell was actively uncovered by the user.
|
||||||
|
* Note: This is not (necessarily) equal to the number of uncovered cells,
|
||||||
|
* because uncovering empty cells uncovers more than one cell. Such an
|
||||||
|
* activity is counted as 'one action' only.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @return The number of times the used uncovered a cell
|
||||||
|
* or 0, if the game is not valid.
|
||||||
|
*/
|
||||||
|
<type> msg_get_uncover_action_count(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides access to the underlying game board. This function is intended
|
||||||
|
* for being used by game visualizer but should not be needed by the game
|
||||||
|
* application.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @return The board used by the game instance or 0, if the game is invalid.
|
||||||
|
*/
|
||||||
|
<type> msg_get_board(<params>);
|
||||||
64
ms_main_driver.c
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Klasse: n/a
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Mine Sweeper
|
||||||
|
* Author: */<your name>/*
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* The Mine Sweeper Application.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "ms_game.h"
|
||||||
|
#include "ms_visualizer.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompts the user for the mode of the game to start and returns it.
|
||||||
|
*/
|
||||||
|
<type> ui_prompt_for_mode(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ui_branch handles the user interface mode of the application.
|
||||||
|
* @see main
|
||||||
|
*/
|
||||||
|
<type> ui_branch(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function evaluates the number of command line arguments.
|
||||||
|
* If the user passed one main switches into test mode, i.e., that
|
||||||
|
* the function test_branch() is called and the command line argument
|
||||||
|
* is handed over to this function. If no command line argument is given
|
||||||
|
* main switches into user interface mode and delegates the handling
|
||||||
|
* of this to the function ui_branch().
|
||||||
|
* @see ui_branch.
|
||||||
|
*/
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
<type> mode = BEGINNER;
|
||||||
|
/* can be surrounded with a loop to restart the game in a different mode */
|
||||||
|
mode = ui_prompt_for_mode();
|
||||||
|
ui_branch(mode);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
<type> ui_prompt_for_mode(<params>) {
|
||||||
|
/* can be extended to support interactive mode input */
|
||||||
|
return BEGINNER;
|
||||||
|
}
|
||||||
|
|
||||||
|
<type> ui_branch(<params>)
|
||||||
|
{
|
||||||
|
/* start a new game in the given mode */
|
||||||
|
|
||||||
|
/* present the game - start the loop */
|
||||||
|
/* do { */
|
||||||
|
/* 1. ms_visualize(...) */
|
||||||
|
/* 2. ms_get_input_action() and get the game state */
|
||||||
|
/* 3. evaluate and perform the action */
|
||||||
|
/* until the action to quit the game is commanded and the game is in progress */
|
||||||
|
/* } while(action != QUIT_GAME && state == IN_PROGRESS); */
|
||||||
|
}
|
||||||
131
ms_test_driver.c
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Test Driver for Mine Sweeper
|
||||||
|
* Author: S. Schraml
|
||||||
|
* Due Date: n/a
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Executes all unit tests of Mine Sweeper.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "shortcut.h"
|
||||||
|
#include "test_ms_cell.h"
|
||||||
|
#include "test_ms_board.h"
|
||||||
|
#include "test_ms_ui_utils.h"
|
||||||
|
#include "test_ms_game.h"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
/* CELLS ***********************************/
|
||||||
|
ADD_TEST(test_msc_produce_cell__shall_provide_non_null_cells__for_MAX_x_MAX);
|
||||||
|
ADD_TEST(test_msc_produce_cell__shall_provide_different_cells__for_subsequent_calls);
|
||||||
|
ADD_TEST(test_msc_produce_cell__shall_provide_null_cells__when_out_of_cells);
|
||||||
|
ADD_TEST(test_msc_reset_cell_factory__shall_allow_to_produce_the_all_cells_again);
|
||||||
|
|
||||||
|
ADD_TEST(test_msc_is_valid__shall_be_true__for_valid_cell);
|
||||||
|
ADD_TEST(test_msc_is_valid__shall_be_false__for_null_cell);
|
||||||
|
|
||||||
|
ADD_TEST(test_msc_has_mine__shall_be_false__for_initial_cell);
|
||||||
|
ADD_TEST(test_msc_has_mine__shall_be_false__for_invalid_cell);
|
||||||
|
ADD_TEST(test_msc_has_mine__shall_be_true__after_mine_is_dropped);
|
||||||
|
|
||||||
|
ADD_TEST(test_msc_get_dangerous_neighbor_count__shall_be_0__for_initial_cell);
|
||||||
|
ADD_TEST(test_msc_get_dangerous_neighbor_count__shall_be_255__for_invalid_cell);
|
||||||
|
ADD_TEST(test_msc_get_dangerous_neighbor_count__shall_be_X_up_to_8__after_X_increments);
|
||||||
|
|
||||||
|
ADD_TEST(test_msc_get_marker__shall_provide_NONE__for_initial_cell);
|
||||||
|
ADD_TEST(test_msc_get_marker__shall_provide_NONE__for_invalid_cell);
|
||||||
|
ADD_TEST(test_msc_get_marker__shall_provide_set_marker);
|
||||||
|
|
||||||
|
ADD_TEST(test_msc_is_covered__shall_be_true__for_initial_cell);
|
||||||
|
ADD_TEST(test_msc_is_covered__shall_be_false__for_uncovered_cell);
|
||||||
|
ADD_TEST(test_msc_is_covered__shall_be_false__for_invalid_cell);
|
||||||
|
|
||||||
|
ADD_TEST(test_msc_uncovered__shall_be_true__for_covered_cell);
|
||||||
|
ADD_TEST(test_msc_uncovered__shall_be_true__for_unmarked_cell);
|
||||||
|
ADD_TEST(test_msc_uncovered__shall_be_false__for_invalid_cell);
|
||||||
|
ADD_TEST(test_msc_uncovered__shall_be_false__for_uncovered_cell);
|
||||||
|
ADD_TEST(test_msc_uncovered__shall_be_false__for_marked_cell);
|
||||||
|
|
||||||
|
ADD_TEST(test_msc_is_valid__shall_be_false__for_cell_with_more_than_eight_neighbors);
|
||||||
|
|
||||||
|
/* BOARD ***********************************/
|
||||||
|
ADD_TEST(test_msb_get_board__shall_provide_non_null_board);
|
||||||
|
ADD_TEST(test_msb_get_board__shall_provide_identical_board_instance);
|
||||||
|
|
||||||
|
ADD_TEST(test_msb_init_board__shall_provide_valid_board);
|
||||||
|
ADD_TEST(test_msb_init_board__shall_provide_invalid_board__for_zero_cols);
|
||||||
|
ADD_TEST(test_msb_init_board__shall_provide_invalid_board__for_zero_rows);
|
||||||
|
ADD_TEST(test_msb_init_board__shall_provide_valid_board__for_too_many_cols);
|
||||||
|
ADD_TEST(test_msb_init_board__shall_provide_valid_board__for_too_many_rows);
|
||||||
|
|
||||||
|
ADD_TEST(test_msb_is_valid__shall_be_true__for_valid_board);
|
||||||
|
ADD_TEST(test_msb_is_valid__shall_be_false__for_null_board);
|
||||||
|
ADD_TEST(test_msb_is_valid__shall_be_false__for_zero_dimensions);
|
||||||
|
|
||||||
|
ADD_TEST(test_msb_get_cell__shall_provide_a_non_null_cell__for_idx_within_board_size);
|
||||||
|
ADD_TEST(test_msb_get_cell__shall_provide_a_null_cell__for_idx_outside_board_size);
|
||||||
|
|
||||||
|
ADD_TEST(test_msb_init_board__shall_provide_valid_covered_empty_cells);
|
||||||
|
|
||||||
|
ADD_TEST(test_msb_get_column_count__shall_be_configured_count__for_valid_board);
|
||||||
|
ADD_TEST(test_msb_get_column_count__shall_be_zero__for_invalid_board);
|
||||||
|
ADD_TEST(test_msb_get_row_count__shall_be_configured_count__for_valid_board);
|
||||||
|
ADD_TEST(test_msb_get_row_count__shall_be_zero__for_invalid_board);
|
||||||
|
|
||||||
|
/* UI UTILS ***********************************/
|
||||||
|
ADD_TEST(test_random);
|
||||||
|
ADD_TEST(test_convert_column_addr);
|
||||||
|
ADD_TEST(test_convert_actions);
|
||||||
|
ADD_TEST(test_convert_row_addr);
|
||||||
|
ADD_TEST(test_get_marker_symbol);
|
||||||
|
ADD_TEST(test_get_mine_symbol);
|
||||||
|
ADD_TEST(test_get_status_label);
|
||||||
|
|
||||||
|
/* GAME ***********************************/
|
||||||
|
ADD_TEST(test_msg_start_configured_game__shall_provide_valid_game);
|
||||||
|
ADD_TEST(test_msg_start_game__shall_provide_valid_game__for_valid_arguments);
|
||||||
|
|
||||||
|
ADD_TEST(test_msg_is_valid__shall_be_true__for_valid_game);
|
||||||
|
ADD_TEST(test_msg_is_valid__shall_be_false__for_0_game);
|
||||||
|
ADD_TEST(test_msg_is_valid__shall_be_false__for_game_with_zero_sized_board);
|
||||||
|
|
||||||
|
ADD_TEST(test_msg_get_board__provides_valid_board__for_valid_game);
|
||||||
|
ADD_TEST(test_msg_get_board__provides_invalid_board__for_invalid_game);
|
||||||
|
|
||||||
|
ADD_TEST(test_msg_select_cell__shall_be_true__for_covered_cell_in_range);
|
||||||
|
ADD_TEST(test_msg_select_cell__shall_be_false__for_invalid_game);
|
||||||
|
ADD_TEST(test_msg_select_cell__shall_be_false__for_uncovered_cell_in_range);
|
||||||
|
|
||||||
|
ADD_TEST(test_msg_get_select_cell__shall_provide_cell__for_valid_selection);
|
||||||
|
ADD_TEST(test_msg_get_select_cell__shall_provide_0__for_invalid_selection);
|
||||||
|
|
||||||
|
ADD_TEST(test_msg_uncover_selected_cell__shall_uncover_selected_cell);
|
||||||
|
ADD_TEST(test_msg_uncover_selected_cell__shall_ignore_covered_cell);
|
||||||
|
ADD_TEST(test_msg_uncover_selected_cell__shall_not_crash_on_invalid_game);
|
||||||
|
|
||||||
|
ADD_TEST(test_msg_mark_selected_cell__shall_mark_selected_cell_and_ignore_subsequent_calls);
|
||||||
|
ADD_TEST(test_msg_mark_selected_cell__shall_ignore_mine_detection_if_number_of_mines_is_reached);
|
||||||
|
ADD_TEST(test_msg_mark_selected_cell__shall_clear_detection_marker);
|
||||||
|
ADD_TEST(test_msg_mark_selected_cell__shall_clear_suspicion_marker);
|
||||||
|
|
||||||
|
ADD_TEST(test_msg_get_state__in_progress);
|
||||||
|
ADD_TEST(test_msg_get_state__failed);
|
||||||
|
ADD_TEST(test_msg_get_state__success);
|
||||||
|
ADD_TEST(test_msg_get_state__invalid_game);
|
||||||
|
|
||||||
|
ADD_TEST(test_msg_start_game__is_initialization_valid);
|
||||||
|
ADD_TEST(test_msg_start_game__no_mine_distributed_before_the_first_cell_is_uncovered);
|
||||||
|
ADD_TEST(test_msg_start_game__first_uncovered_cell_is_empty);
|
||||||
|
ADD_TEST(test_msg_start_game__verify_dangerous_neighbor_counts);
|
||||||
|
|
||||||
|
ADD_TEST(test_msg_get_mines_left_count);
|
||||||
|
ADD_TEST(test_msg_get_mines_suspected_count);
|
||||||
|
ADD_TEST(test_msg_get_uncover_action_count);
|
||||||
|
|
||||||
|
run_tests();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
26
ms_ui_utils.c
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Class: <your class>
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Mine Sweeper User Interface Utilities
|
||||||
|
* Author: */<your name>/*
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Implementation of ms_ui_utils.h.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
|
||||||
|
void msu_init_rand() {
|
||||||
|
srand(time(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
CellIdx msu_get_random_index(Count upper_limit) {
|
||||||
|
return (upper_limit == 0 ? 0 : rand() % (upper_limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
118
ms_ui_utils.h
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Class: <your class>
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Mine Sweeper User Interface Utilities
|
||||||
|
* Author: */<your name>/*
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* The declaration of a library providing utility functions
|
||||||
|
* for user interface (UI) related conversions.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the random number generator. It must be invoked
|
||||||
|
* at least once before function ´msu_get_random_index´ can be used.
|
||||||
|
*/
|
||||||
|
void msu_init_rand();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a random cell index from 0 (included) to the given upper limit (excluded).
|
||||||
|
* Note that ´msu_init_rand()´ must be invoked at least during application
|
||||||
|
* runtime before this function is used.
|
||||||
|
*
|
||||||
|
* @return A cell index from 0 to ´upper_limit´.
|
||||||
|
*/
|
||||||
|
CellIdx msu_get_random_index(Count upper_limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the given cell index to the corresponding column address.
|
||||||
|
*
|
||||||
|
* @param idx The index to map.
|
||||||
|
* @return ColAddr The corresponding column address or a '0' value,
|
||||||
|
* if the index cannot be mapped.
|
||||||
|
*/
|
||||||
|
<type> msu_idx_to_col_address(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the given cell index to the corresponding row address.
|
||||||
|
*
|
||||||
|
* @param idx The index to map.
|
||||||
|
* @return RowAddr The corresponding row address or a '0' value,
|
||||||
|
* if the index cannot be mapped.
|
||||||
|
*/
|
||||||
|
<type> msu_idx_to_row_address(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the given column address to the corresponding cell index.
|
||||||
|
*
|
||||||
|
* @param addr The column address to map.
|
||||||
|
* @return CellIdx The corresponding cell index or 0,
|
||||||
|
* if the address cannot be mapped.
|
||||||
|
*/
|
||||||
|
<type> msu_col_address_to_index(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the given row address to the corresponding cell index.
|
||||||
|
*
|
||||||
|
* @param addr The row address to map.
|
||||||
|
* @return CellIdx The corresponding cell index or 0,
|
||||||
|
* if the address cannot be mapped.
|
||||||
|
*/
|
||||||
|
<type> msu_row_address_to_index(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the given column address to the corresponding cell index.
|
||||||
|
*
|
||||||
|
* @param addr The column address to map.
|
||||||
|
* @return CellIdx The corresponding cell index or 0,
|
||||||
|
* if the address cannot be mapped.
|
||||||
|
*/
|
||||||
|
<type> msu_col_address_to_index(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the character a user may type to perform the given action.
|
||||||
|
* The key must be unique for each action.
|
||||||
|
*
|
||||||
|
* @param action The action for which the corresponding 'key' is requested.
|
||||||
|
* @return char The corresponding 'key' character or '\0',
|
||||||
|
* if the action cannot reasonably be mapped.
|
||||||
|
*/
|
||||||
|
<type> msu_get_action_char(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the action for the given 'key' character (the counterpart of ´msu_get_action_char´).
|
||||||
|
*
|
||||||
|
* @param key The key for the action.
|
||||||
|
* @return Action The action corresponding to the given 'key'
|
||||||
|
* or 'INVALID',if the key cannot be mapped.
|
||||||
|
*/
|
||||||
|
<type> msu_get_action(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the given cell marker to its presentation symbol
|
||||||
|
* (the character shown in the game visualization).
|
||||||
|
*
|
||||||
|
* @param marker The marker.
|
||||||
|
* @return char The corresponding character or '#', if
|
||||||
|
* the marker cannot be mapped.
|
||||||
|
*/
|
||||||
|
<type> msu_get_marker_symbol(<params>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the symbol that represents a mine
|
||||||
|
* (the character shown in the game visualization).
|
||||||
|
*
|
||||||
|
* @return char The symbol of a mine.
|
||||||
|
*/
|
||||||
|
<type> msu_get_mine_symbol();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the given game status to a user presentable label.
|
||||||
|
*
|
||||||
|
* @param status The status to map.
|
||||||
|
* @return char* The text for the status or 0, if
|
||||||
|
* the status cannot be mapped.
|
||||||
|
*/
|
||||||
|
<type> msu_get_status_label(<params>);
|
||||||
438
ms_visualizer.c
Normal file
|
|
@ -0,0 +1,438 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Class: n/a
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Mine Sweeper Visualizer
|
||||||
|
* Author: S. Schraml
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Implementation of Mine Sweeper Visualizer.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ms_visualizer.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "ms_board.h"
|
||||||
|
#include "ms_cell.h"
|
||||||
|
#include "ms_ui_utils.h"
|
||||||
|
|
||||||
|
/* The inner width of a grid cell in horizontal and verticel direction */
|
||||||
|
#define CELL_WIDTH 3
|
||||||
|
/* The grid separation symbols */
|
||||||
|
#define SEP '|'
|
||||||
|
#define CROSS '+'
|
||||||
|
#define LINE '-'
|
||||||
|
#define BORDER '='
|
||||||
|
|
||||||
|
/* The encapsulated visualizer data */
|
||||||
|
struct VisData {
|
||||||
|
bool disabled;
|
||||||
|
bool cheat;
|
||||||
|
int refresh_line_count;
|
||||||
|
int left_margin;
|
||||||
|
int offset;
|
||||||
|
int width;
|
||||||
|
char input_key;
|
||||||
|
int input_val;
|
||||||
|
int input_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* The visualizer instance */
|
||||||
|
static struct VisData vis_data = {false, false, 0, 4, 2, 0, '\0', 0, 0};
|
||||||
|
|
||||||
|
/* sets the cursor TO given position in the same line */
|
||||||
|
static void set_cursor_hor(int pos) {
|
||||||
|
printf("\033[%dG", pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sets the cursor FOR the given distance */
|
||||||
|
static void move_cursor(int lines, int cols) {
|
||||||
|
if (lines < 0) {
|
||||||
|
printf("\033[%dA", lines * -1);
|
||||||
|
} else if (lines > 0) {
|
||||||
|
printf("\033[%dB", lines);
|
||||||
|
}
|
||||||
|
if (cols < 0) {
|
||||||
|
printf("\033[%dD", cols * -1);
|
||||||
|
} else if (cols > 0) {
|
||||||
|
printf("\033[%dC", cols);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Provides the number output lines for a configured game. */
|
||||||
|
static void print_new_line() {
|
||||||
|
vis_data.refresh_line_count++;
|
||||||
|
printf("\n\033[2K"); /* next line */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Starts a new line */
|
||||||
|
static void start_line(int offset) {
|
||||||
|
printf("\033[2K"); /* clear current line */
|
||||||
|
printf("\033[%dG", vis_data.left_margin + offset); /* set cursor to end of offset */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_terminated_new_line(char terminator) {
|
||||||
|
set_cursor_hor(vis_data.left_margin + vis_data.offset + vis_data.width -1);
|
||||||
|
printf("%c", SEP);
|
||||||
|
print_new_line();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prints the given amount of the given sign. */
|
||||||
|
static void print_sign(char sign, int length) {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
printf("%c", sign);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prints a full-width terminated bar of the given sign. */
|
||||||
|
static void print_terminated_bar_line(char left, char sign, char right, int offset) {
|
||||||
|
start_line(offset);
|
||||||
|
Count w = vis_data.width - (left != '\0' ? 1 : 0) - (right != '\0' ? 1 : 0);
|
||||||
|
printf("%c", left);
|
||||||
|
print_sign(sign, w);
|
||||||
|
printf("%c", right);
|
||||||
|
print_new_line();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prints a full-width bar of the given sign. */
|
||||||
|
static void print_bar_line(char sign, int offset) {
|
||||||
|
start_line(offset);
|
||||||
|
print_sign(sign, vis_data.width);
|
||||||
|
print_new_line();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prints the line of text */
|
||||||
|
static void print_text_line(char* text, int offset) {
|
||||||
|
start_line(offset);
|
||||||
|
printf("%s", text);
|
||||||
|
print_new_line();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_centered(char* left_border, char* text, char* right_border, int offset) {
|
||||||
|
start_line(offset);
|
||||||
|
int gap = (vis_data.width - strnlen(text, 128)) / 2;
|
||||||
|
if (left_border != 0) {
|
||||||
|
printf("%s", left_border);
|
||||||
|
}
|
||||||
|
int marg = gap - strnlen(left_border, 128);
|
||||||
|
if (marg > 0) { print_sign(' ', marg); }
|
||||||
|
if (text != 0) {
|
||||||
|
printf("%s", text);
|
||||||
|
}
|
||||||
|
marg = vis_data.width - gap - strnlen(text, 128) - strnlen(right_border, 128);
|
||||||
|
if (marg > 0) { print_sign(' ', marg); }
|
||||||
|
if (right_border != 0) {
|
||||||
|
printf("%s", right_border);
|
||||||
|
}
|
||||||
|
print_new_line();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prints a full-width grid line with seperator after each cell_width bar. */
|
||||||
|
static void print_grid_line(char sep, char bar, Byte cell_width, int offset) {
|
||||||
|
start_line(offset);
|
||||||
|
Count w = 0;
|
||||||
|
while (w < vis_data.width -1 ) {
|
||||||
|
printf("%c", sep);
|
||||||
|
print_sign(bar, cell_width);
|
||||||
|
w += (cell_width + 1);
|
||||||
|
}
|
||||||
|
printf("%c", sep);
|
||||||
|
print_new_line();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Provides the code to clear applied colors. */
|
||||||
|
static char* get_clear_color() {
|
||||||
|
return "\033[0m";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Provides the color code for the cell */
|
||||||
|
char* get_cell_color(MsCell cell, bool selected, bool unhide) {
|
||||||
|
char* col = get_clear_color();
|
||||||
|
|
||||||
|
if (msc_is_valid(cell)) {
|
||||||
|
char* no_mark_covered = "\033[0;30;47m";
|
||||||
|
char* detected_covered = "\033[1;31;47m";
|
||||||
|
char* suspected_covered = "\033[1;34;47m";
|
||||||
|
|
||||||
|
char* no_mark_selected = "\033[0;30;44m";
|
||||||
|
char* detected_selected = "\033[1;31;44m";
|
||||||
|
char* suspected_selected = "\033[1;33;44m";
|
||||||
|
|
||||||
|
char* empty_open = "\033[0;30;40m";
|
||||||
|
char* one_open = "\033[1;34;40m";
|
||||||
|
char* two_open = "\033[1;35;40m";
|
||||||
|
char* three_open = "\033[1;36;40m";
|
||||||
|
char* four_open = "\033[1;32;40m";
|
||||||
|
char* five_open = "\033[1;33;40m";
|
||||||
|
char* six_open = "\033[0;33;40m";
|
||||||
|
char* seven_open = "\033[1;37;40m";
|
||||||
|
char* eight_open = "\033[1;31;40m";
|
||||||
|
|
||||||
|
char* mine_detonated = "\033[1;37;41m";
|
||||||
|
char* mine_missed = "\033[1;36;47m";
|
||||||
|
char* mine_wrong = "\033[1;31;47m";
|
||||||
|
|
||||||
|
if (msc_is_covered(cell)) {
|
||||||
|
CellMarker marker = msc_get_marker(cell);
|
||||||
|
switch (marker) {
|
||||||
|
case NONE: col = selected ? no_mark_selected : no_mark_covered; break;
|
||||||
|
case MINE_DETECTED: col = selected ? detected_selected : detected_covered; break;
|
||||||
|
case MINE_SUSPECTED: col = selected ? suspected_selected : suspected_covered; break;
|
||||||
|
}
|
||||||
|
if (unhide) {
|
||||||
|
if (marker == MINE_DETECTED && !msc_has_mine(cell)) {
|
||||||
|
col = mine_wrong;
|
||||||
|
} else if (marker != MINE_DETECTED && msc_has_mine(cell)) {
|
||||||
|
col = mine_missed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (msc_has_mine(cell)) {
|
||||||
|
col = mine_detonated;
|
||||||
|
} else {
|
||||||
|
/* uncovered cell without mine */
|
||||||
|
switch (msc_get_dangerous_neighbor_count(cell)) {
|
||||||
|
case 0: col = empty_open; break;
|
||||||
|
case 1: col = one_open; break;
|
||||||
|
case 2: col = two_open; break;
|
||||||
|
case 3: col = three_open; break;
|
||||||
|
case 4: col = four_open; break;
|
||||||
|
case 5: col = five_open; break;
|
||||||
|
case 6: col = six_open; break;
|
||||||
|
case 7: col = seven_open; break;
|
||||||
|
case 8: col = eight_open; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Provides the symbol of the cell */
|
||||||
|
static char get_cell_symbol(MsCell cell, bool unhide) {
|
||||||
|
char symbol = ' ';
|
||||||
|
if (msc_is_valid(cell)) {
|
||||||
|
if (msc_is_covered(cell)) {
|
||||||
|
CellMarker marker = msc_get_marker(cell);
|
||||||
|
symbol = msu_get_marker_symbol(marker);
|
||||||
|
if (unhide) {
|
||||||
|
if ( (marker == MINE_DETECTED && !msc_has_mine(cell))
|
||||||
|
|| (marker != MINE_DETECTED && msc_has_mine(cell))) {
|
||||||
|
symbol = msu_get_mine_symbol();
|
||||||
|
}
|
||||||
|
} else if (vis_data.cheat) {
|
||||||
|
printf("\033[1D%c",
|
||||||
|
(msc_has_mine(cell)
|
||||||
|
? msu_get_mine_symbol()
|
||||||
|
: '0' + msc_get_dangerous_neighbor_count(cell))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (msc_has_mine(cell)) {
|
||||||
|
symbol = msu_get_mine_symbol();
|
||||||
|
} else {
|
||||||
|
Byte dnc = msc_get_dangerous_neighbor_count(cell);
|
||||||
|
symbol = dnc > 0 ? '0' + dnc : ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prints a full-width grid line with seperator after each cell_width bar. */
|
||||||
|
static void print_board_column_header(MsBoard board, char sep) {
|
||||||
|
start_line(vis_data.offset);
|
||||||
|
Count margin = (CELL_WIDTH - 1) / 2;
|
||||||
|
Count col_cnt = msb_get_column_count(board);
|
||||||
|
printf("%c", sep);
|
||||||
|
for (CellIdx col_idx = 0; col_idx < col_cnt; col_idx++) {
|
||||||
|
print_sign(' ', margin);
|
||||||
|
printf("%d", msu_idx_to_col_address(col_idx));
|
||||||
|
print_sign(' ', CELL_WIDTH - 1 - margin);
|
||||||
|
printf("%c", sep);
|
||||||
|
}
|
||||||
|
print_new_line();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prints a full-width grid line with seperator after each cell_width bar. */
|
||||||
|
static void print_board_row(MsBoard board, MsCell selected_cell, CellIdx row_idx, char sep, bool show_symbols, bool unhide) {
|
||||||
|
start_line(vis_data.offset - 2);
|
||||||
|
Count margin = (CELL_WIDTH - 1) / 2;
|
||||||
|
Count col_cnt = msb_get_column_count(board);
|
||||||
|
printf("%c %c", msu_idx_to_row_address(row_idx), sep);
|
||||||
|
for (CellIdx col_idx = 0; col_idx < col_cnt; col_idx++) {
|
||||||
|
MsCell cell = msb_get_cell(board, col_idx, row_idx);
|
||||||
|
printf("%s", get_cell_color(cell, cell == selected_cell, unhide));
|
||||||
|
print_sign(' ', (show_symbols ? margin : CELL_WIDTH));
|
||||||
|
if (show_symbols) {
|
||||||
|
printf("%c", get_cell_symbol(cell, unhide));
|
||||||
|
print_sign(' ', CELL_WIDTH - 1 - margin);
|
||||||
|
}
|
||||||
|
printf("%s%c", get_clear_color(), sep);
|
||||||
|
}
|
||||||
|
print_new_line();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prepares the frame to print */
|
||||||
|
static void prepare_frame(MsGame game) {
|
||||||
|
printf("\033[%dA", vis_data.refresh_line_count); /* move cursor to the first line of the game output */
|
||||||
|
vis_data.refresh_line_count = 0;
|
||||||
|
vis_data.width = (msg_is_valid(game)
|
||||||
|
? msb_get_column_count(msg_get_board(game)) * (CELL_WIDTH + 1) + 1
|
||||||
|
: 0);
|
||||||
|
print_new_line();
|
||||||
|
print_new_line();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prints the game header */
|
||||||
|
static void print_header(MsGame game) {
|
||||||
|
if(!msg_is_valid(game)) {
|
||||||
|
print_text_line(" ====================================", vis_data.offset);
|
||||||
|
print_text_line(" ==--- GAME IS NOT VALID ---==", vis_data.offset);
|
||||||
|
print_text_line(" ====================================", vis_data.offset);
|
||||||
|
} else {
|
||||||
|
print_terminated_bar_line(CROSS, BORDER, CROSS, vis_data.offset);
|
||||||
|
print_centered("=-- ", "MINE SWEEPER", " --=", vis_data.offset);
|
||||||
|
print_terminated_bar_line(CROSS, BORDER, CROSS, vis_data.offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prints the status bar of the game */
|
||||||
|
static void print_status_bar(MsGame game) {
|
||||||
|
if (msg_is_valid(game)) {
|
||||||
|
start_line(vis_data.offset);
|
||||||
|
printf("%c %c: %3d %c %c: %3d %c A: %3d %c",
|
||||||
|
SEP, msu_get_mine_symbol(), msg_get_mines_left_count(game),
|
||||||
|
SEP, msu_get_marker_symbol(MINE_SUSPECTED), msg_get_mines_suspected_count(game),
|
||||||
|
SEP, msg_get_uncover_action_count(game),
|
||||||
|
SEP);
|
||||||
|
print_terminated_new_line(SEP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prints the board */
|
||||||
|
static void print_board(MsGame game, bool unhide) {
|
||||||
|
if (msg_is_valid(game)) {
|
||||||
|
MsBoard board = msg_get_board(game);
|
||||||
|
MsCell selected_cell = msg_get_selected_cell(game);
|
||||||
|
Count row_cnt = msb_get_row_count(board);
|
||||||
|
int cell_height = 1; /* CELL_WIDTH */
|
||||||
|
int center_idx = cell_height / 2;
|
||||||
|
// print_grid_line(CROSS, LINE, CELL_WIDTH, vis_data.offset);
|
||||||
|
print_bar_line(' ', vis_data.offset);
|
||||||
|
print_board_column_header(board, SEP);
|
||||||
|
print_grid_line(CROSS, LINE, CELL_WIDTH, vis_data.offset);
|
||||||
|
for (CellIdx row_idx = 0; row_idx < row_cnt; row_idx++) {
|
||||||
|
for (int line_idx = 0; line_idx < cell_height; line_idx++) {
|
||||||
|
print_board_row(board, selected_cell, row_idx, SEP, line_idx == center_idx, unhide);
|
||||||
|
}
|
||||||
|
print_grid_line(CROSS, LINE, CELL_WIDTH, vis_data.offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prints the input bar */
|
||||||
|
static void print_input_bar(MsGame game) {
|
||||||
|
if (msg_is_valid(game)) {
|
||||||
|
start_line(vis_data.offset);
|
||||||
|
GameState state = msg_get_state(game);
|
||||||
|
if (state == FAILED || state == SOLVED) {
|
||||||
|
printf("%c Game completed:", SEP);
|
||||||
|
print_terminated_new_line(SEP);
|
||||||
|
start_line(vis_data.offset);
|
||||||
|
printf("%c %s", SEP, msu_get_status_label(state));
|
||||||
|
print_terminated_new_line(SEP);
|
||||||
|
print_terminated_bar_line(CROSS, BORDER, CROSS, vis_data.offset);
|
||||||
|
} else {
|
||||||
|
MsCell selected_cell = msg_get_selected_cell(game);
|
||||||
|
char* msg = "";
|
||||||
|
Count input_pos = vis_data.left_margin + vis_data.offset + 3;
|
||||||
|
if (msc_is_valid(selected_cell)) {
|
||||||
|
printf("%c Actions: '%c' uncover, %c' quit game",
|
||||||
|
SEP,
|
||||||
|
msu_get_action_char(UNCOVER),
|
||||||
|
msu_get_action_char(QUIT_GAME)
|
||||||
|
);
|
||||||
|
print_terminated_new_line(SEP);
|
||||||
|
start_line(vis_data.offset);
|
||||||
|
printf("%c '%c' mark mine, '%c' suspect mine, '%c' clear marker",
|
||||||
|
SEP,
|
||||||
|
msu_get_action_char(MARK_MINE),
|
||||||
|
msu_get_action_char(MARK_SUSPECT),
|
||||||
|
msu_get_action_char(QUIT_GAME)
|
||||||
|
);
|
||||||
|
msg = "Action (or cell)?";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
printf("%c Select a cell by entering", SEP);
|
||||||
|
print_terminated_new_line(SEP);
|
||||||
|
start_line(vis_data.offset);
|
||||||
|
printf("%c its address e.g. '%c%d'", SEP, msu_idx_to_row_address(0), msu_idx_to_col_address(0));
|
||||||
|
msg = "Cell?";
|
||||||
|
}
|
||||||
|
print_terminated_new_line(SEP);
|
||||||
|
start_line(vis_data.offset);
|
||||||
|
input_pos += strnlen(msg, 32);
|
||||||
|
printf("%c %s ", SEP, msg);
|
||||||
|
print_terminated_new_line(SEP);
|
||||||
|
print_terminated_bar_line(CROSS, BORDER, CROSS, vis_data.offset);
|
||||||
|
|
||||||
|
vis_data.input_key = '\0';
|
||||||
|
vis_data.input_val = 0;
|
||||||
|
char buf[6];
|
||||||
|
move_cursor(-2, 0);
|
||||||
|
set_cursor_hor(input_pos);
|
||||||
|
if (fgets(buf, sizeof(buf)/sizeof(buf[0]), stdin)) {
|
||||||
|
vis_data.input_count = sscanf(buf, "%c%d", &vis_data.input_key, &vis_data.input_val);
|
||||||
|
}
|
||||||
|
move_cursor(1, 0);
|
||||||
|
print_new_line();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ms_disable_visualizer(bool disable) {
|
||||||
|
vis_data.disabled = disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ms_cheat(bool enable) {
|
||||||
|
vis_data.cheat = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ms_visualize(MsGame game) {
|
||||||
|
prepare_frame(game);
|
||||||
|
print_header(game);
|
||||||
|
print_status_bar(game);
|
||||||
|
print_board(game, msg_get_state(game) == FAILED);
|
||||||
|
print_input_bar(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action ms_get_input_action() {
|
||||||
|
Action action = UNKNOWN;
|
||||||
|
if (vis_data.input_count == 1) {
|
||||||
|
action = msu_get_action(vis_data.input_key);
|
||||||
|
} else if (vis_data.input_count == 2) {
|
||||||
|
action = CELL_SELECT;
|
||||||
|
}
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
ColAddr ms_get_input_column() {
|
||||||
|
ColAddr addr = MAX_BOARD_SIZE;
|
||||||
|
if (vis_data.input_count == 2) {
|
||||||
|
addr = vis_data.input_val;
|
||||||
|
}
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
RowAddr ms_get_input_row() {
|
||||||
|
RowAddr addr = '\0';
|
||||||
|
if (vis_data.input_count == 2) {
|
||||||
|
addr = vis_data.input_key;
|
||||||
|
}
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
81
ms_visualizer.h
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Class: n/a
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Mine Sweeper Visualizer
|
||||||
|
* Author: S. Schraml
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Prints the Mine Sweeper game to standard output
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
#ifndef ___MS_VISUALIZER_H
|
||||||
|
#define ___MS_VISUALIZER_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "ms_game.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies whether or not visualization is enabled.
|
||||||
|
* With enabled visualization, the game is shown on
|
||||||
|
* standard output. Disabled visualization does
|
||||||
|
* neither print the board nor does it add a delay.
|
||||||
|
*
|
||||||
|
* Default value is enabled. This feature is intended for
|
||||||
|
* automatic testing.
|
||||||
|
*
|
||||||
|
* @param disable True to disable, false to enable the visualizer.
|
||||||
|
*/
|
||||||
|
void ms_disable_visualizer(bool disable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies whether or not 'cheating' is enabled.
|
||||||
|
* When cheating is enabled, the content of covered
|
||||||
|
* cells is printed without uncovering the cell.
|
||||||
|
*
|
||||||
|
* @param enable True to enable the cheat mode,
|
||||||
|
* false to disable it.
|
||||||
|
*/
|
||||||
|
void ms_cheat(bool enable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints the current state including the board of the
|
||||||
|
* given Mine Sweeper game.
|
||||||
|
*
|
||||||
|
* @param game The Mine Sweeper game to visualize.
|
||||||
|
*/
|
||||||
|
void ms_visualize(MsGame game);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the action the user entered by the user. If
|
||||||
|
* the user entered a cell address, the action ´CELL_SELECT´
|
||||||
|
* is returned. Therefore the action can be used to determine
|
||||||
|
* whether a cell shall be selected or an activity shall be
|
||||||
|
* performed. The address of the selected cell can be retrieved
|
||||||
|
* via ´ms_get_input_column()´ and ´ms_get_input_row()´.
|
||||||
|
*
|
||||||
|
* @return Action The last action that was enter by the user
|
||||||
|
* or ´UNKNOWN´, if the input was not recognized.
|
||||||
|
*/
|
||||||
|
Action ms_get_input_action();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the column address of the cell entered by the user,
|
||||||
|
* if ´ms_get_input_action()´ returned ´CELL_SELECT´.
|
||||||
|
*
|
||||||
|
* @return ColAddr The entered column address of a cell or
|
||||||
|
* an invalid value.
|
||||||
|
*/
|
||||||
|
ColAddr ms_get_input_column();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the row address of the cell entered by the user,
|
||||||
|
* if ´ms_get_input_action()´ returned ´CELL_SELECT´.
|
||||||
|
*
|
||||||
|
* @return RowAddr The entered row address of a cell
|
||||||
|
* or an invalid value.
|
||||||
|
*/
|
||||||
|
RowAddr ms_get_input_row();
|
||||||
|
|
||||||
|
#endif
|
||||||
151
shortcut.c
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Title: shortcut.c
|
||||||
|
* Author: P. Bauer
|
||||||
|
* Date: November 08, 2010
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Test driver.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#include "shortcut.h"
|
||||||
|
|
||||||
|
#define MAX_TEST_FUNCTIONS 256
|
||||||
|
|
||||||
|
static char assert_msg_buffer[1024];
|
||||||
|
static int tc_count = 0;
|
||||||
|
static int tc_fail_count = 0;
|
||||||
|
|
||||||
|
static struct TestCase test_cases[MAX_TEST_FUNCTIONS];
|
||||||
|
|
||||||
|
const char* version()
|
||||||
|
{
|
||||||
|
return "ShortCut v. 1.3.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
char* format_msg(char* format, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start (args, format);
|
||||||
|
vsprintf(assert_msg_buffer, format, args);
|
||||||
|
return assert_msg_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void assert_true(bool bool_expr, struct TestCase *tc, const char *msg,
|
||||||
|
const char* file, int line)
|
||||||
|
{
|
||||||
|
if (!bool_expr) {
|
||||||
|
if (tc->success) {
|
||||||
|
tc->success = false;
|
||||||
|
tc_fail_count++;
|
||||||
|
}
|
||||||
|
printf("\n\tFailure (file: %s, line %d): %s: %s", file, line, tc->name, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void assert_false(bool bool_expr, struct TestCase *tc, const char *msg,
|
||||||
|
const char* file, int line)
|
||||||
|
{
|
||||||
|
assert_true(!bool_expr, tc, msg, file, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assert_string_failure(const char *expected, char *actual, struct TestCase *tc,
|
||||||
|
const char *msg, const char* file, int line);
|
||||||
|
|
||||||
|
void assert_equals_str(const char *expected, char *actual, struct TestCase *tc,
|
||||||
|
const char *msg, const char* file, int line)
|
||||||
|
{
|
||||||
|
if (expected == actual) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expected == 0 || actual == 0) {
|
||||||
|
assert_string_failure(expected, actual, tc, msg, file, line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(actual, expected) != 0) {
|
||||||
|
assert_string_failure(expected, actual, tc, msg, file, line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MAX_MSG_LEN 128
|
||||||
|
static void assert_string_failure(const char *expected, char *actual, struct TestCase *tc,
|
||||||
|
const char *msg, const char* file, int line)
|
||||||
|
{
|
||||||
|
char new_msg[MAX_MSG_LEN];
|
||||||
|
|
||||||
|
sprintf(new_msg, "Expected \"%s\", actual \"%s\". %s", expected, actual, msg);
|
||||||
|
assert_true(false, tc, new_msg, file, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assert_equals(int expected, int actual, struct TestCase *tc,
|
||||||
|
const char *msg, const char* file, int line)
|
||||||
|
{
|
||||||
|
char new_msg[MAX_MSG_LEN];
|
||||||
|
sprintf(new_msg, "Expected %d, actual %d. %s", expected, actual, msg);
|
||||||
|
assert_true(expected == actual, tc, new_msg, file, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assert_equals_f(double expected, double actual, double tolerance, struct TestCase* tc,
|
||||||
|
const char* msg, const char* file, int line)
|
||||||
|
{
|
||||||
|
char new_msg[MAX_MSG_LEN];
|
||||||
|
sprintf(new_msg, "Expected %f, actual %f. %s", expected, actual, msg);
|
||||||
|
double min_val = expected - tolerance;
|
||||||
|
double max_val = expected + tolerance;
|
||||||
|
assert_true(min_val <= actual && actual <= max_val, tc, new_msg, file, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_test_count()
|
||||||
|
{
|
||||||
|
return tc_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool add_test(void (*test_function)(struct TestCase *tc), const char *test_name)
|
||||||
|
{
|
||||||
|
if (tc_count == MAX_TEST_FUNCTIONS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
test_cases[tc_count].success = true;
|
||||||
|
test_cases[tc_count].name = test_name;
|
||||||
|
test_cases[tc_count].test_function = test_function;
|
||||||
|
tc_count++;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_tests()
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
printf("\n%s: Running tests\n", version());
|
||||||
|
|
||||||
|
for (i = 0; i < get_test_count(); i++) {
|
||||||
|
printf("Running test %s ...", test_cases[i].name);
|
||||||
|
test_cases[i].test_function(&test_cases[i]);
|
||||||
|
if (test_cases[i].success) {
|
||||||
|
printf("\033[32m OK\033[m");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("\033[31m ... FAIL\033[m");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\nTotal tests run: %d\n", tc_count);
|
||||||
|
if (tc_fail_count > 0) {
|
||||||
|
printf("\033[31mTests failed: %d\033[m\n", tc_fail_count);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("\033[32mAll tests run successfully\033[m\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
110
shortcut.h
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Title: shortcut
|
||||||
|
* Author: P. Bauer
|
||||||
|
* Date: November 03, 2010
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* A simple unit testing frame work for C.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
#ifndef ___SHORTCUT_H
|
||||||
|
#define ___SHORTCUT_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/** TestCase is the struct to define one test case. A test case can
|
||||||
|
*** be added to a test. If the test is run all added test cases are
|
||||||
|
*** run and the result of the run of each test case is checked automatically.
|
||||||
|
*/
|
||||||
|
struct TestCase {
|
||||||
|
const char *name;
|
||||||
|
/** true if the test passed, false otherwise. */
|
||||||
|
bool success;
|
||||||
|
|
||||||
|
/** The test function which is executed by the test framework. */
|
||||||
|
void (*test_function)(struct TestCase *tc);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*** @return Version of shortcut as string
|
||||||
|
***/
|
||||||
|
const char* version();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*** @return The fromated string as generated using sprintf(format, ...)
|
||||||
|
***/
|
||||||
|
char* format_msg(char* format, ...);
|
||||||
|
|
||||||
|
/** assert_true checks, whether a boolean expression passed is true or false.
|
||||||
|
*** in case it is false the test case stating the assertion is marked
|
||||||
|
*** as failed and msg is printed.
|
||||||
|
*** @param bool_expr Expression which is evaluated.
|
||||||
|
*** @param tc Pointer to the test case which states this assertion.
|
||||||
|
*** @param msg Message to be printed if assertion evaluates to false.
|
||||||
|
*** @param file File in which the assert is given.
|
||||||
|
*** @param line Line in which the assert is given.
|
||||||
|
*/
|
||||||
|
void assert_true(bool bool_expr, struct TestCase *tc, const char *msg,
|
||||||
|
const char* file, int line);
|
||||||
|
|
||||||
|
/** assert_false does the same as assert() but the boolean expression
|
||||||
|
*** has to evaluate to false. If it evaluates to true the assertion
|
||||||
|
*** fails.
|
||||||
|
*** @see assert
|
||||||
|
*/
|
||||||
|
void assert_false(bool bool_expr, struct TestCase* tc, const char* msg,
|
||||||
|
const char* file, int line);
|
||||||
|
|
||||||
|
/** assert_equals checks whether two values are equal. Currently the following
|
||||||
|
*** data formats are supported:
|
||||||
|
*** - strings
|
||||||
|
*** - integer
|
||||||
|
*** @param expected The expected string value
|
||||||
|
*** @param actual The actual string value
|
||||||
|
*** @param tc Pointer to the test case which states this assertion.
|
||||||
|
*** @param msg Message to be printed if assertion evaluates to false.
|
||||||
|
*** @param file File in which the assert is given.
|
||||||
|
*** @param line Line in which the assert is given.
|
||||||
|
*** @see assert
|
||||||
|
*/
|
||||||
|
void assert_equals(int expected, int actual, struct TestCase* tc,
|
||||||
|
const char* msg, const char* file, int line);
|
||||||
|
void assert_equals_str(const char* expected, char* actual, struct TestCase* tc,
|
||||||
|
const char* msg, const char* file, int line);
|
||||||
|
void assert_equals_f(double expected, double actual, double tolerance, struct TestCase* tc,
|
||||||
|
const char* msg, const char* file, int line);
|
||||||
|
|
||||||
|
/** @return The total number of test cases added to the test.
|
||||||
|
*/
|
||||||
|
int get_test_count();
|
||||||
|
|
||||||
|
/** add_test creates a new test case and adds the a test function to
|
||||||
|
*** this test case.
|
||||||
|
*** @param test_function Pointer to the test function to be added
|
||||||
|
*** to the newly created test case.
|
||||||
|
*** @param test_case_name Name which should be assigned to the newly
|
||||||
|
*** created test case.
|
||||||
|
*/
|
||||||
|
bool add_test(void (*test_function)(struct TestCase *tc), const char *test_case_name);
|
||||||
|
|
||||||
|
void run_tests();
|
||||||
|
|
||||||
|
#define TEST(testname) void testname(struct TestCase *tc)
|
||||||
|
|
||||||
|
#define MSG(format, ...) format_msg(format, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#define ASSERT_TRUE(condition, msg) assert_true(condition, tc, msg, __FILE__, __LINE__)
|
||||||
|
|
||||||
|
#define ASSERT_FALSE(condition, msg) assert_false(condition, tc, msg, __FILE__, __LINE__)
|
||||||
|
|
||||||
|
#define ASSERT_EQUALS(expected, actual) assert_equals(expected, actual, tc, "", __FILE__, __LINE__)
|
||||||
|
|
||||||
|
#define ASSERT_EQUALS_STR(expected, actual) assert_equals_str(expected, actual, tc, "", __FILE__, __LINE__)
|
||||||
|
|
||||||
|
#define ASSERT_EQUALS_TOLERANCE(expected, actual, tolerance) assert_equals_f(expected, actual, tolerance, tc, "", __FILE__, __LINE__)
|
||||||
|
|
||||||
|
#define ADD_TEST(testfunction) add_test(testfunction, #testfunction)
|
||||||
|
|
||||||
|
#endif
|
||||||
186
test_ms_board.c
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Klasse: n/a
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Implementation of UTs for Mine Sweeper Board
|
||||||
|
* Author: S. Schraml
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Test functions for ADT MsBoard.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "test_ms_board.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "shortcut.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "ms_board.h"
|
||||||
|
#include "ms_cell.h"
|
||||||
|
|
||||||
|
TEST(test_msb_get_board__shall_provide_non_null_board) {
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
ASSERT_TRUE(board != 0, MSG("Expected non-null board"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_get_board__shall_provide_identical_board_instance) {
|
||||||
|
MsBoard board1 = msb_get_board();
|
||||||
|
MsBoard board2 = msb_get_board();
|
||||||
|
ASSERT_TRUE(board1 == board2, MSG("Expected the same board on subsequent calls"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_init_board__shall_provide_valid_board) {
|
||||||
|
Count cols = 3;
|
||||||
|
Count rows = 2;
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
|
||||||
|
msb_init_board(board, cols, rows);
|
||||||
|
ASSERT_TRUE(msb_is_valid(board), MSG("Expected valid board"));
|
||||||
|
|
||||||
|
msb_init_board(board, MAX_BOARD_SIZE, MAX_BOARD_SIZE);
|
||||||
|
ASSERT_TRUE(msb_is_valid(board), MSG("Expected valid board"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_init_board__shall_provide_invalid_board__for_zero_cols) {
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
msb_init_board(board, 0, 1);
|
||||||
|
ASSERT_FALSE(msb_is_valid(board), MSG("Expected invalid board"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_init_board__shall_provide_invalid_board__for_zero_rows) {
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
msb_init_board(board, 1, 0);
|
||||||
|
ASSERT_FALSE(msb_is_valid(board), MSG("Expected invalid board"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_init_board__shall_provide_valid_board__for_too_many_cols) {
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
msb_init_board(board, MAX_BOARD_SIZE + 1, 1);
|
||||||
|
ASSERT_TRUE(msb_is_valid(board), MSG("Expected valid board"));
|
||||||
|
Count cnt = msb_get_column_count(board);
|
||||||
|
ASSERT_TRUE(cnt == MAX_BOARD_SIZE, MSG("Expected column count clipped to %d but was", MAX_BOARD_SIZE, cnt));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_init_board__shall_provide_valid_board__for_too_many_rows) {
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
msb_init_board(board, 1, MAX_BOARD_SIZE + 1);
|
||||||
|
ASSERT_TRUE(msb_is_valid(board), MSG("Expected valid board"));
|
||||||
|
Count cnt = msb_get_row_count(board);
|
||||||
|
ASSERT_TRUE(cnt == MAX_BOARD_SIZE, MSG("Expected row count clipped to %d but was", MAX_BOARD_SIZE, cnt));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_init_board__shall_provide_valid_covered_empty_cells) {
|
||||||
|
Count cols = 3;
|
||||||
|
Count rows = 2;
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
|
||||||
|
msb_init_board(board, cols, rows);
|
||||||
|
ASSERT_TRUE(msb_is_valid(board), MSG("Expected valid board"));
|
||||||
|
|
||||||
|
for (int row = 0; row < rows; row++) {
|
||||||
|
for (int col = 0; col < cols; col++) {
|
||||||
|
MsCell cell = msb_get_cell(board, col, row);
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell at %d/%d", col, row));
|
||||||
|
ASSERT_TRUE(msc_is_covered(cell), MSG("Expected covered cell at %d/%d", col, row));
|
||||||
|
ASSERT_TRUE(msc_get_dangerous_neighbor_count(cell) == 0, MSG("Expected empty cell at %d/%d", col, row));
|
||||||
|
ASSERT_TRUE(msc_get_marker(cell) == NONE, MSG("Expected unmarked cell at %d/%d", col, row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_is_valid__shall_be_true__for_valid_board) {
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
msb_init_board(board, 1, 1);
|
||||||
|
ASSERT_TRUE(msb_is_valid(board), MSG("Expected valid board"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_is_valid__shall_be_false__for_null_board) {
|
||||||
|
ASSERT_FALSE(msb_is_valid(0), MSG("Expected valid board"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_is_valid__shall_be_false__for_zero_dimensions) {
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
msb_init_board(board, 0, 0);
|
||||||
|
ASSERT_FALSE(msb_is_valid(board), MSG("Expected invalid board"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_get_cell__shall_provide_a_non_null_cell__for_idx_within_board_size) {
|
||||||
|
Count cols = 2;
|
||||||
|
Count rows = 3;
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
|
||||||
|
msb_init_board(board, cols, rows);
|
||||||
|
ASSERT_TRUE(msb_is_valid(board), MSG("Expected valid board"));
|
||||||
|
|
||||||
|
for (int row = 0; row < rows; row++) {
|
||||||
|
for (int col = 0; col < cols; col++) {
|
||||||
|
ASSERT_TRUE(msb_get_cell(board, col, row) != 0, MSG("Expected non-null cell at %d/%d", col, row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_get_cell__shall_provide_a_null_cell__for_idx_outside_board_size) {
|
||||||
|
Count cols = 4;
|
||||||
|
Count rows = 2;
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
|
||||||
|
msb_init_board(board, cols, rows);
|
||||||
|
ASSERT_TRUE(msb_is_valid(board), MSG("Expected valid board"));
|
||||||
|
|
||||||
|
for (int row = rows; row < MAX_BOARD_SIZE + 1; row++) {
|
||||||
|
for (int col = cols; col < MAX_BOARD_SIZE + 1; col++) {
|
||||||
|
ASSERT_TRUE(msb_get_cell(board, col, row) == 0, MSG("Expected null cell at %d/%d", col, row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_get_column_count__shall_be_configured_count__for_valid_board) {
|
||||||
|
Count exp_cols = 8;
|
||||||
|
Count exp_rows = 7;
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
msb_init_board(board, exp_cols, exp_rows);
|
||||||
|
ASSERT_TRUE(msb_is_valid(board), MSG("Expected valid board"));
|
||||||
|
|
||||||
|
Count act_cols = msb_get_column_count(board);
|
||||||
|
ASSERT_TRUE(act_cols == exp_cols, MSG("Expected column count of %d, but was %d", exp_cols, act_cols));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_get_column_count__shall_be_zero__for_invalid_board) {
|
||||||
|
Count act_cols = msb_get_column_count(0);
|
||||||
|
ASSERT_TRUE(act_cols == 0, MSG("Expected column count of 0 for invalid board, but was", act_cols));
|
||||||
|
|
||||||
|
Count exp_count = 9;
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
|
||||||
|
msb_init_board(board, exp_count, 0);
|
||||||
|
ASSERT_FALSE(msb_is_valid(board), MSG("Expected invalid board"));
|
||||||
|
act_cols = msb_get_column_count(board);
|
||||||
|
ASSERT_TRUE(act_cols == 0, MSG("Expected column count of 0 for invalid board, but was", act_cols));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_get_row_count__shall_be_configured_count__for_valid_board) {
|
||||||
|
Count exp_cols = 6;
|
||||||
|
Count exp_rows = 9;
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
msb_init_board(board, exp_cols, exp_rows);
|
||||||
|
ASSERT_TRUE(msb_is_valid(board), MSG("Expected valid board"));
|
||||||
|
|
||||||
|
Count act_rows = msb_get_row_count(board);
|
||||||
|
ASSERT_TRUE(act_rows == exp_rows, MSG("Expected row count of %d, but was %d", exp_rows, act_rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msb_get_row_count__shall_be_zero__for_invalid_board) {
|
||||||
|
Count act_rows = msb_get_column_count(0);
|
||||||
|
ASSERT_TRUE(act_rows == 0, MSG("Expected row count of 0 for invalid board, but was", act_rows));
|
||||||
|
|
||||||
|
Count exp_count = 7;
|
||||||
|
MsBoard board = msb_get_board();
|
||||||
|
|
||||||
|
msb_init_board(board, 0, exp_count);
|
||||||
|
ASSERT_FALSE(msb_is_valid(board), MSG("Expected invalid board"));
|
||||||
|
act_rows = msb_get_row_count(board);
|
||||||
|
ASSERT_TRUE(act_rows == 0, MSG("Expected row count of 0 for invalid board, but was", act_rows));
|
||||||
|
}
|
||||||
39
test_ms_board.h
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Klasse: n/a
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Unit Tests for Mine Sweeper Board
|
||||||
|
* Author: S. Schraml
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Test functions for ADT MsBoard.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
#ifndef ___TEST_MS_BOARD_H
|
||||||
|
#define ___TEST_MS_BOARD_H
|
||||||
|
|
||||||
|
#include "shortcut.h"
|
||||||
|
|
||||||
|
TEST(test_msb_get_board__shall_provide_non_null_board);
|
||||||
|
TEST(test_msb_get_board__shall_provide_identical_board_instance);
|
||||||
|
|
||||||
|
TEST(test_msb_init_board__shall_provide_valid_board);
|
||||||
|
TEST(test_msb_init_board__shall_provide_invalid_board__for_zero_cols);
|
||||||
|
TEST(test_msb_init_board__shall_provide_invalid_board__for_zero_rows);
|
||||||
|
TEST(test_msb_init_board__shall_provide_valid_board__for_too_many_cols);
|
||||||
|
TEST(test_msb_init_board__shall_provide_valid_board__for_too_many_rows);
|
||||||
|
TEST(test_msb_init_board__shall_provide_valid_covered_empty_cells);
|
||||||
|
|
||||||
|
TEST(test_msb_is_valid__shall_be_true__for_valid_board);
|
||||||
|
TEST(test_msb_is_valid__shall_be_false__for_null_board);
|
||||||
|
TEST(test_msb_is_valid__shall_be_false__for_zero_dimensions);
|
||||||
|
|
||||||
|
TEST(test_msb_get_cell__shall_provide_a_non_null_cell__for_idx_within_board_size);
|
||||||
|
TEST(test_msb_get_cell__shall_provide_a_null_cell__for_idx_outside_board_size);
|
||||||
|
|
||||||
|
TEST(test_msb_get_column_count__shall_be_configured_count__for_valid_board);
|
||||||
|
TEST(test_msb_get_column_count__shall_be_zero__for_invalid_board);
|
||||||
|
TEST(test_msb_get_row_count__shall_be_configured_count__for_valid_board);
|
||||||
|
TEST(test_msb_get_row_count__shall_be_zero__for_invalid_board);
|
||||||
|
|
||||||
|
#endif
|
||||||
238
test_ms_cell.c
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Klasse: n/a
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Implementation of UTs for Mine Sweeper Cell
|
||||||
|
* Author: S. Schraml
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Test functions for ADT MsCell.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "test_ms_cell.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "shortcut.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "ms_cell.h"
|
||||||
|
|
||||||
|
TEST(test_msc_produce_cell__shall_provide_non_null_cells__for_MAX_x_MAX) {
|
||||||
|
MsCell cell = 0;
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
for (int i = 0; i < MAX_BOARD_SIZE * MAX_BOARD_SIZE; i++) {
|
||||||
|
cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(cell != 0, MSG("Expected non-null cell"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_produce_cell__shall_provide_different_cells__for_subsequent_calls) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell1 = msc_produce_cell();
|
||||||
|
MsCell cell2 = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(cell1 != cell2, MSG("Expected different cells [%p] vs. [%p]", cell1, cell2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_produce_cell__shall_provide_null_cells__when_out_of_cells) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
for (int i = 0; i < MAX_BOARD_SIZE * MAX_BOARD_SIZE; i++) {
|
||||||
|
msc_produce_cell();
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(msc_produce_cell() == 0, MSG("Expected null cell"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_reset_cell_factory__shall_allow_to_produce_the_all_cells_again) {
|
||||||
|
test_msc_produce_cell__shall_provide_non_null_cells__for_MAX_x_MAX(tc);
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
test_msc_produce_cell__shall_provide_non_null_cells__for_MAX_x_MAX(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_is_valid__shall_be_true__for_valid_cell) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(cell != 0, MSG("Expected non-null cell"));
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
msc_inc_dangerous_neighbor_count(cell);
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected cell with 1 neighbor being valid"));
|
||||||
|
for (int i = 2; i <= 8; i++) {
|
||||||
|
msc_inc_dangerous_neighbor_count(cell);
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected cell with 8 neighbors being valid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_is_valid__shall_be_false__for_null_cell) {
|
||||||
|
ASSERT_FALSE(msc_is_valid(0), MSG("Expected null cell to be invalid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_is_valid__shall_be_false__for_cell_with_more_than_eight_neighbors) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
for (int i = 0; i <= 9; i++) {
|
||||||
|
msc_inc_dangerous_neighbor_count(cell);
|
||||||
|
}
|
||||||
|
ASSERT_FALSE(msc_is_valid(cell), MSG("Expected cell with 9 neighbors being invalid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_has_mine__shall_be_false__for_initial_cell) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
ASSERT_FALSE(msc_has_mine(cell), MSG("Expected cell has no mine"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_has_mine__shall_be_false__for_invalid_cell) {
|
||||||
|
ASSERT_FALSE(msc_has_mine(0), MSG("Expected cell has no mine"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_has_mine__shall_be_true__after_mine_is_dropped) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
ASSERT_FALSE(msc_has_mine(cell), MSG("Expected cell has no mine"));
|
||||||
|
msc_drop_mine(cell);
|
||||||
|
ASSERT_TRUE(msc_has_mine(cell), MSG("#1 Expected cell has mine"));
|
||||||
|
msc_drop_mine(cell);
|
||||||
|
ASSERT_TRUE(msc_has_mine(cell), MSG("#2 Expected cell has mine"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_get_dangerous_neighbor_count__shall_be_0__for_initial_cell) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
Byte dnc = msc_get_dangerous_neighbor_count(cell);
|
||||||
|
ASSERT_TRUE(dnc == 0, MSG("Expected dangerous neighbor count is 0, but was %d", dnc));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_get_dangerous_neighbor_count__shall_be_255__for_invalid_cell) {
|
||||||
|
Byte dnc = msc_get_dangerous_neighbor_count(0);
|
||||||
|
ASSERT_TRUE(dnc == 255, MSG("Expected dangerous neighbor count is 0, but was %d", dnc));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_get_dangerous_neighbor_count__shall_be_X_up_to_8__after_X_increments) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
for (int i = 1; i < 10; i++) {
|
||||||
|
msc_inc_dangerous_neighbor_count(cell);
|
||||||
|
Byte dnc = msc_get_dangerous_neighbor_count(cell);
|
||||||
|
int exp_dnc = i <= 8 ? i : 0xFF;
|
||||||
|
ASSERT_TRUE(dnc == exp_dnc, MSG("Expected dangerous neighbor count is %d, but was %d", exp_dnc, dnc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_get_marker__shall_provide_NONE__for_initial_cell) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
CellMarker marker = msc_get_marker(cell);
|
||||||
|
ASSERT_TRUE(marker == NONE, MSG("Expected cell marker is %d, but was %d", NONE, marker));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_get_marker__shall_provide_NONE__for_invalid_cell) {
|
||||||
|
CellMarker marker = msc_get_marker(0);
|
||||||
|
ASSERT_TRUE(marker == NONE, MSG("Expected cell marker is %d, but was %d", NONE, marker));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_get_marker__shall_provide_set_marker) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
CellMarker exp_markers[] = {MINE_DETECTED, MINE_SUSPECTED, NONE, MINE_SUSPECTED};
|
||||||
|
for (int i = 0; i < sizeof(exp_markers)/exp_markers[0]; i++) {
|
||||||
|
msc_set_marker(cell, exp_markers[i]);
|
||||||
|
CellMarker act_marker = msc_get_marker(cell);
|
||||||
|
ASSERT_TRUE(act_marker == exp_markers[i], MSG("Expected cell marker is %d, but was %d", exp_markers[i], act_marker));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_is_covered__shall_be_true__for_initial_cell) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
ASSERT_TRUE(msc_is_covered(cell), MSG("Expected valid cell"));
|
||||||
|
|
||||||
|
/* and again */
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
ASSERT_TRUE(msc_is_covered(cell), MSG("Expected valid cell"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_is_covered__shall_be_false__for_uncovered_cell) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
ASSERT_TRUE(msc_is_covered(cell), MSG("Expected covered cell"));
|
||||||
|
|
||||||
|
msc_uncover(cell);
|
||||||
|
ASSERT_FALSE(msc_is_covered(cell), MSG("Expected uncovered cell"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_is_covered__shall_be_false__for_invalid_cell) {
|
||||||
|
ASSERT_FALSE(msc_is_covered(0), MSG("#1 Expected invalid cell being covered"));
|
||||||
|
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
for (int i = 0; i <= 9; i++) {
|
||||||
|
msc_inc_dangerous_neighbor_count(cell);
|
||||||
|
}
|
||||||
|
ASSERT_FALSE(msc_is_valid(cell), MSG("#2 Expected invalid cell being covered"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_uncovered__shall_be_true__for_covered_cell) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
ASSERT_TRUE(msc_uncover(cell), MSG("Expected cell being successfully uncovered"));
|
||||||
|
ASSERT_FALSE(msc_is_covered(cell), MSG("Expected cell being uncovered"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_uncovered__shall_be_true__for_unmarked_cell) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
msc_set_marker(cell, MINE_DETECTED);
|
||||||
|
msc_set_marker(cell, NONE);
|
||||||
|
ASSERT_TRUE(msc_uncover(cell), MSG("Expected cell uncovering returns true"));
|
||||||
|
ASSERT_FALSE(msc_is_covered(cell), MSG("Expected cell became eventually uncovered"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_uncovered__shall_be_false__for_invalid_cell) {
|
||||||
|
ASSERT_FALSE(msc_uncover(0), MSG("#1 Expected uncovering invalid cell returns false"));
|
||||||
|
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
for (int i = 0; i <= 9; i++) {
|
||||||
|
msc_inc_dangerous_neighbor_count(cell);
|
||||||
|
}
|
||||||
|
ASSERT_FALSE(msc_uncover(cell), MSG("#2 Expected uncovering invalid cell returns false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_uncovered__shall_be_false__for_uncovered_cell) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
msc_uncover(cell);
|
||||||
|
ASSERT_FALSE(msc_uncover(cell), MSG("Expected cell uncovering returns false"));
|
||||||
|
ASSERT_FALSE(msc_is_covered(cell), MSG("Expected cell being uncovered"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msc_uncovered__shall_be_false__for_marked_cell) {
|
||||||
|
msc_reset_cell_factory();
|
||||||
|
MsCell cell = msc_produce_cell();
|
||||||
|
ASSERT_TRUE(msc_is_valid(cell), MSG("Expected valid cell"));
|
||||||
|
ASSERT_TRUE(msc_is_covered(cell), MSG("Expected initially cell is covered"));
|
||||||
|
msc_set_marker(cell, MINE_DETECTED);
|
||||||
|
ASSERT_FALSE(msc_uncover(cell), MSG("#1 Expected cell uncovering returns false"));
|
||||||
|
ASSERT_TRUE(msc_is_covered(cell), MSG("#2 Expected cell remains covered"));
|
||||||
|
msc_set_marker(cell, MINE_SUSPECTED);
|
||||||
|
ASSERT_FALSE(msc_uncover(cell), MSG("#3 Expected cell uncovering returns false 2"));
|
||||||
|
ASSERT_TRUE(msc_is_covered(cell), MSG("#4 Expected cell remains covered"));
|
||||||
|
}
|
||||||
49
test_ms_cell.h
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Klasse: n/a
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Unit Tests for Mine Sweeper Cell
|
||||||
|
* Author: S. Schraml
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Test functions for ADT MsCell.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
#ifndef ___TEST_MS_CELL_H
|
||||||
|
#define ___TEST_MS_CELL_H
|
||||||
|
|
||||||
|
#include "shortcut.h"
|
||||||
|
|
||||||
|
TEST(test_msc_produce_cell__shall_provide_non_null_cells__for_MAX_x_MAX);
|
||||||
|
TEST(test_msc_produce_cell__shall_provide_different_cells__for_subsequent_calls);
|
||||||
|
TEST(test_msc_produce_cell__shall_provide_null_cells__when_out_of_cells);
|
||||||
|
|
||||||
|
TEST(test_msc_reset_cell_factory__shall_allow_to_produce_the_all_cells_again);
|
||||||
|
|
||||||
|
TEST(test_msc_is_valid__shall_be_true__for_valid_cell);
|
||||||
|
TEST(test_msc_is_valid__shall_be_false__for_null_cell);
|
||||||
|
TEST(test_msc_is_valid__shall_be_false__for_cell_with_more_than_eight_neighbors);
|
||||||
|
|
||||||
|
TEST(test_msc_has_mine__shall_be_false__for_initial_cell);
|
||||||
|
TEST(test_msc_has_mine__shall_be_false__for_invalid_cell);
|
||||||
|
TEST(test_msc_has_mine__shall_be_true__after_mine_is_dropped);
|
||||||
|
|
||||||
|
TEST(test_msc_get_dangerous_neighbor_count__shall_be_0__for_initial_cell);
|
||||||
|
TEST(test_msc_get_dangerous_neighbor_count__shall_be_255__for_invalid_cell);
|
||||||
|
TEST(test_msc_get_dangerous_neighbor_count__shall_be_X_up_to_8__after_X_increments);
|
||||||
|
|
||||||
|
TEST(test_msc_get_marker__shall_provide_NONE__for_initial_cell);
|
||||||
|
TEST(test_msc_get_marker__shall_provide_NONE__for_invalid_cell);
|
||||||
|
TEST(test_msc_get_marker__shall_provide_set_marker);
|
||||||
|
|
||||||
|
TEST(test_msc_is_covered__shall_be_true__for_initial_cell);
|
||||||
|
TEST(test_msc_is_covered__shall_be_false__for_uncovered_cell);
|
||||||
|
TEST(test_msc_is_covered__shall_be_false__for_invalid_cell);
|
||||||
|
|
||||||
|
TEST(test_msc_uncovered__shall_be_true__for_covered_cell);
|
||||||
|
TEST(test_msc_uncovered__shall_be_true__for_unmarked_cell);
|
||||||
|
TEST(test_msc_uncovered__shall_be_false__for_invalid_cell);
|
||||||
|
TEST(test_msc_uncovered__shall_be_false__for_uncovered_cell);
|
||||||
|
TEST(test_msc_uncovered__shall_be_false__for_marked_cell);
|
||||||
|
|
||||||
|
#endif
|
||||||
494
test_ms_game.c
Normal file
|
|
@ -0,0 +1,494 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Klasse: n/a
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Implementation of UTs for Mine Sweeper Game
|
||||||
|
* Author: S. Schraml
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Test functions for ADT MsGame.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "test_ms_game.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "shortcut.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "ms_game.h"
|
||||||
|
#include "ms_board.h"
|
||||||
|
#include "ms_ui_utils.h"
|
||||||
|
|
||||||
|
|
||||||
|
TEST(test_msg_start_configured_game__shall_provide_valid_game) {
|
||||||
|
GameMode modes[3] = { BEGINNER, ADVANCED, EXPERT };
|
||||||
|
for (int i = 0; i < sizeof(modes)/ sizeof(modes[0]); i++) {
|
||||||
|
MsGame game = msg_start_configured_game(modes[i]);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game for mode %d", modes[i]));
|
||||||
|
}
|
||||||
|
MsGame game = msg_start_configured_game(CUSTOM);
|
||||||
|
ASSERT_FALSE(msg_is_valid(game), MSG("Expected invalid game for 'configured' custom mode"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_start_game__shall_provide_valid_game__for_valid_arguments) {
|
||||||
|
MsGame game = msg_start_game(3, 4, 2);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game for custom mode"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_is_valid__shall_be_true__for_valid_game) {
|
||||||
|
MsGame game = msg_start_game(MAX_BOARD_SIZE, MAX_BOARD_SIZE, 3);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_is_valid__shall_be_false__for_0_game) {
|
||||||
|
ASSERT_FALSE(msg_is_valid(0), MSG("Expected invalid game"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_is_valid__shall_be_false__for_game_with_zero_sized_board) {
|
||||||
|
MsGame game = msg_start_game(0, 8, 3);
|
||||||
|
ASSERT_FALSE(msg_is_valid(game), MSG("#1 Expected invalid game"));
|
||||||
|
game = msg_start_game(8, 0, 3);
|
||||||
|
ASSERT_FALSE(msg_is_valid(game), MSG("#2 Expected invalid game"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_select_cell__shall_be_true__for_covered_cell_in_range) {
|
||||||
|
MsGame game = msg_start_configured_game(BEGINNER);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
CellIdx col_idx = 1;
|
||||||
|
CellIdx row_idx = 2;
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
ColAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
ASSERT_TRUE(msc_is_covered(msb_get_cell(msg_get_board(game), col_idx, row_idx)), MSG("Expected covered cell"));
|
||||||
|
ASSERT_TRUE(msg_select_cell(game, col_addr, row_addr), MSG("Expected successful selection"));
|
||||||
|
col_addr = msu_idx_to_col_address(col_idx + 1);
|
||||||
|
row_addr = msu_idx_to_row_address(row_idx + 1);
|
||||||
|
ASSERT_TRUE(msg_select_cell(game, col_addr, row_addr), MSG("Expected successful selection"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_select_cell__shall_be_false__for_invalid_game) {
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(0);
|
||||||
|
ColAddr row_addr = msu_idx_to_row_address(0);
|
||||||
|
ASSERT_FALSE(msg_select_cell(0, col_addr, row_addr), MSG("Expected unsuccessful selection"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_select_cell__shall_be_false__for_uncovered_cell_in_range) {
|
||||||
|
MsGame game = msg_start_configured_game(BEGINNER);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
CellIdx col_idx = 2;
|
||||||
|
CellIdx row_idx = 1;
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
ColAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
MsCell cell = msb_get_cell(msg_get_board(game), col_idx, row_idx);
|
||||||
|
msc_uncover(cell);
|
||||||
|
ASSERT_FALSE(msc_is_covered(cell), MSG("Expected uncovered cell"));
|
||||||
|
ASSERT_FALSE(msg_select_cell(game, col_addr, row_addr), MSG("Expected unsuccessful selection"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_get_select_cell__shall_provide_cell__for_valid_selection) {
|
||||||
|
MsGame game = msg_start_configured_game(BEGINNER);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
CellIdx col_idx = 1;
|
||||||
|
CellIdx row_idx = 2;
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
ColAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
MsCell cell = msb_get_cell(msg_get_board(game), col_idx, row_idx);
|
||||||
|
bool rc = msg_select_cell(game, col_addr, row_addr);
|
||||||
|
ASSERT_TRUE(rc, MSG("Expected successful selection"));
|
||||||
|
ASSERT_TRUE(msg_get_selected_cell(game) == cell, MSG("Expected selected cell"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_get_select_cell__shall_provide_0__for_invalid_selection) {
|
||||||
|
MsGame game = msg_start_configured_game(BEGINNER);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
CellIdx col_idx = msb_get_column_count(msg_get_board(game));
|
||||||
|
CellIdx row_idx = 1;
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
ColAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
ASSERT_TRUE(msg_get_selected_cell(game) == 0, MSG("#1 Expected selected cell to be 0"));
|
||||||
|
bool rc = msg_select_cell(game, col_addr, row_addr);
|
||||||
|
ASSERT_FALSE(rc, MSG("Expected unsuccessful selection"));
|
||||||
|
ASSERT_TRUE(msg_get_selected_cell(game) == 0, MSG("#2 Expected selected cell to be 0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_uncover_selected_cell__shall_uncover_selected_cell) {
|
||||||
|
MsGame game = msg_start_configured_game(BEGINNER);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
CellIdx col_idx = 3;
|
||||||
|
CellIdx row_idx = 3;
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
RowAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
MsCell cell = msb_get_cell(msg_get_board(game), col_idx, row_idx);
|
||||||
|
ASSERT_TRUE(msc_is_covered(cell), MSG("Expected covered cell"));
|
||||||
|
ASSERT_TRUE(msg_select_cell(game, col_addr, row_addr), MSG("Expected successful selection"));
|
||||||
|
msg_uncover_selected_cell(game);
|
||||||
|
ASSERT_FALSE(msc_is_covered(cell), MSG("Expected uncovered cell"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_uncover_selected_cell__shall_ignore_covered_cell) {
|
||||||
|
MsGame game = msg_start_configured_game(BEGINNER);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
CellIdx col_idx = 3;
|
||||||
|
CellIdx row_idx = 3;
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
RowAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
MsCell cell = msb_get_cell(msg_get_board(game), col_idx, row_idx);
|
||||||
|
msc_uncover(cell);
|
||||||
|
ASSERT_FALSE(msc_is_covered(cell), MSG("Expected uncovered cell"));
|
||||||
|
ASSERT_FALSE(msg_select_cell(game, col_addr, row_addr), MSG("Expected unsuccessful selection"));
|
||||||
|
msg_uncover_selected_cell(game);
|
||||||
|
ASSERT_FALSE(msc_is_covered(cell), MSG("Expected uncovered cell"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_uncover_selected_cell__shall_not_crash_on_invalid_game) {
|
||||||
|
msg_uncover_selected_cell(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_mark_selected_cell__shall_mark_selected_cell_and_ignore_subsequent_calls) {
|
||||||
|
MsGame game = msg_start_configured_game(BEGINNER);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
CellIdx col_idx = 4;
|
||||||
|
CellIdx row_idx = 1;
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
RowAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
MsCell cell = msb_get_cell(msg_get_board(game), col_idx, row_idx);
|
||||||
|
ASSERT_TRUE(msc_get_marker(cell) == NONE, MSG("Expected cell with marker 'NONE'"));
|
||||||
|
|
||||||
|
CellMarker markers[] = {MINE_DETECTED, MINE_SUSPECTED, NONE};
|
||||||
|
for (int i = 0; i < sizeof(markers)/sizeof(markers[0]); i++) {
|
||||||
|
ASSERT_TRUE(msg_select_cell(game, col_addr, row_addr), MSG("Expected successful selection"));
|
||||||
|
msg_mark_selected_cell(game, markers[i]);
|
||||||
|
ASSERT_TRUE(msc_get_marker(cell) == markers[i], MSG("#1 Expected cell with marker '%d'", markers[i]));
|
||||||
|
msg_mark_selected_cell(game, NONE);
|
||||||
|
ASSERT_TRUE(msc_get_marker(cell) == markers[i], MSG("#2 Expected cell with marker '%d'", markers[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_mark_selected_cell__shall_ignore_mine_detection_if_number_of_mines_is_reached) {
|
||||||
|
Count cnt = 10;
|
||||||
|
MsGame game = msg_start_game(cnt, cnt, cnt);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
for (int i = 0; i < cnt + 2; i++) {
|
||||||
|
CellIdx col_idx = i % cnt;
|
||||||
|
CellIdx row_idx = i / cnt;
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
RowAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
MsCell cell = msb_get_cell(msg_get_board(game), col_idx, row_idx);
|
||||||
|
msg_select_cell(game, col_addr, row_addr);
|
||||||
|
msg_mark_selected_cell(game, MINE_DETECTED);
|
||||||
|
if (i < cnt) {
|
||||||
|
ASSERT_TRUE(msc_get_marker(cell) == MINE_DETECTED, MSG("#%d: Expected cell with mine detected marker", i));
|
||||||
|
ASSERT_TRUE(msg_get_mines_left_count(game) == (cnt - i - 1), MSG("#%d: Expected %d mines left", i, (cnt - i - 1)));
|
||||||
|
} else {
|
||||||
|
ASSERT_TRUE(msc_get_marker(cell) == NONE, MSG("#%d: Expected mine detected marker count does not exceed number of mines", i));
|
||||||
|
ASSERT_TRUE(msg_get_mines_left_count(game) == 0, MSG("#%d: Expected 0 mines left", i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ASSERT_CLEAR_MARKER(marker_to_clear) _assert_clear_marker(tc, marker_to_clear)
|
||||||
|
void _assert_clear_marker(struct TestCase* tc, CellMarker marker_to_clear) {
|
||||||
|
MsGame game = msg_start_configured_game(BEGINNER);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
CellIdx col_idx = 4;
|
||||||
|
CellIdx row_idx = 1;
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
RowAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
MsCell cell = msb_get_cell(msg_get_board(game), col_idx, row_idx);
|
||||||
|
ASSERT_TRUE(msc_get_marker(cell) == NONE, MSG("Expected cell with marker 'NONE'"));
|
||||||
|
|
||||||
|
Count mine_cnt = msg_get_mines_left_count(game);
|
||||||
|
CellMarker markers[] = {MINE_DETECTED, MINE_SUSPECTED, NONE};
|
||||||
|
for (int i = 0; i < sizeof(markers)/sizeof(markers[0]); i++) {
|
||||||
|
ASSERT_TRUE(msg_select_cell(game, col_addr, row_addr), MSG("Expected successful selection"));
|
||||||
|
msg_mark_selected_cell(game, marker_to_clear);
|
||||||
|
ASSERT_TRUE(msc_get_marker(cell) == marker_to_clear, MSG("Expected cell with marker 'MINE_DETECTED'"));
|
||||||
|
Count exp_marker_cnt = 1;
|
||||||
|
Count act_marker_cnt = (marker_to_clear == MINE_DETECTED ? mine_cnt - msg_get_mines_left_count(game) : msg_get_mines_suspected_count(game));
|
||||||
|
ASSERT_TRUE(act_marker_cnt == exp_marker_cnt, MSG("Expected 'mines left' count of %d, but was %d", exp_marker_cnt, act_marker_cnt));
|
||||||
|
|
||||||
|
ASSERT_TRUE(msg_select_cell(game, col_addr, row_addr), MSG("Expected successful selection"));
|
||||||
|
msg_mark_selected_cell(game, markers[i]);
|
||||||
|
ASSERT_TRUE(msc_get_marker(cell) == markers[i], MSG("Expected cell with marker '%d'", markers[i]));
|
||||||
|
act_marker_cnt = (marker_to_clear == MINE_DETECTED ? mine_cnt - msg_get_mines_left_count(game) : msg_get_mines_suspected_count(game));
|
||||||
|
exp_marker_cnt = (markers[i] == marker_to_clear ? 1 : 0);
|
||||||
|
ASSERT_TRUE(act_marker_cnt == exp_marker_cnt, MSG("Expected 'mines left' count of %d, but was %d", exp_marker_cnt, act_marker_cnt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_mark_selected_cell__shall_clear_detection_marker) {
|
||||||
|
ASSERT_CLEAR_MARKER(MINE_DETECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_mark_selected_cell__shall_clear_suspicion_marker) {
|
||||||
|
ASSERT_CLEAR_MARKER(MINE_SUSPECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_start_game__is_initialization_valid) {
|
||||||
|
Count cols = 9;
|
||||||
|
Count rows = 11;
|
||||||
|
Count mines = 10;
|
||||||
|
MsGame game = msg_start_game(cols, rows, mines);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
ASSERT_TRUE(msg_get_state(game) == IN_PROGRESS, MSG("Expected game in progress"));
|
||||||
|
|
||||||
|
|
||||||
|
MsCell cell = 0;
|
||||||
|
Count invalid_count = 0;
|
||||||
|
Count uncovered_count = 0;
|
||||||
|
Count mine_count = 0;
|
||||||
|
MsBoard board = msg_get_board(game);
|
||||||
|
|
||||||
|
for (CellIdx r = 0; r < rows; r++) {
|
||||||
|
for (CellIdx c = 0; c < cols; c++) {
|
||||||
|
cell = msb_get_cell(board, c, r);
|
||||||
|
invalid_count += (msc_is_valid(cell) ? 0 : 1);
|
||||||
|
uncovered_count += (msc_is_covered(cell) ? 0 : 1);
|
||||||
|
mine_count += (msc_has_mine(cell) ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(invalid_count == 0, MSG("Expected 0 invalid cells but found %d", invalid_count));
|
||||||
|
ASSERT_TRUE(uncovered_count == 0, MSG("Expected 0 uncovered cells after start but found %d", uncovered_count));
|
||||||
|
ASSERT_TRUE(mine_count == 0, MSG("Expected 0 mines after start but found %d", mine_count));
|
||||||
|
|
||||||
|
/* uncover the first cell */
|
||||||
|
CellIdx start_col = cols - 1;
|
||||||
|
CellIdx start_row = 0;
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(start_col);
|
||||||
|
RowAddr row_addr = msu_idx_to_row_address(start_row);
|
||||||
|
msg_select_cell(game, col_addr, row_addr);
|
||||||
|
msg_uncover_selected_cell(game);
|
||||||
|
|
||||||
|
cell = msb_get_cell(board, start_col, start_row);
|
||||||
|
ASSERT_FALSE(msc_is_covered(cell), MSG("Expected first uncovered cell."));
|
||||||
|
ASSERT_FALSE(msc_has_mine(cell), MSG("Expected first cell has no mine."));
|
||||||
|
ASSERT_TRUE(msc_get_dangerous_neighbor_count(cell) == 0, MSG("Expected first cell is an empty cell."));
|
||||||
|
|
||||||
|
invalid_count = 0;
|
||||||
|
uncovered_count = 0;
|
||||||
|
mine_count = 0;
|
||||||
|
for (CellIdx r = 0; r < rows; r++) {
|
||||||
|
for (CellIdx c = 0; c < cols; c++) {
|
||||||
|
cell = msb_get_cell(board, c, r);
|
||||||
|
invalid_count += (msc_is_valid(cell) ? 0 : 1);
|
||||||
|
uncovered_count += (msc_is_covered(cell) ? 0 : 1);
|
||||||
|
mine_count += (msc_has_mine(cell) ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(invalid_count == 0, MSG("Expected 0 invalid cells but found %d", invalid_count));
|
||||||
|
ASSERT_TRUE(uncovered_count > 1, MSG("Expected more than 1 uncovered cells after start but found %d", uncovered_count));
|
||||||
|
ASSERT_TRUE(mine_count == mines, MSG("Expected %d mines after start but found %d", mines, mine_count));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_get_state__in_progress) {
|
||||||
|
MsGame game = msg_start_configured_game(BEGINNER);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
ASSERT_TRUE(msg_get_state(game) == IN_PROGRESS, MSG("Expected game in progress"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_get_state__failed) {
|
||||||
|
MsGame game = msg_start_configured_game(BEGINNER);
|
||||||
|
CellIdx col_idx = 0;
|
||||||
|
CellIdx row_idx = 0;
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
RowAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
MsCell cell = msb_get_cell(msg_get_board(game), col_idx, row_idx);
|
||||||
|
msc_drop_mine(cell);
|
||||||
|
msg_select_cell(game, col_addr, row_addr);
|
||||||
|
msg_uncover_selected_cell(game);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected failed game"));
|
||||||
|
ASSERT_TRUE(msg_get_state(game) == FAILED, MSG("Expected failed game"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_get_state__success) {
|
||||||
|
Count max = 5;
|
||||||
|
Count mine_cnt = 0;
|
||||||
|
MsGame game = msg_start_game(max, max, max);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected failed game"));
|
||||||
|
for (CellIdx row_idx = 0; row_idx < max; row_idx++) {
|
||||||
|
for (CellIdx col_idx = 0; col_idx < max; col_idx++) {
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
RowAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
MsCell cell = msb_get_cell(msg_get_board(game), col_idx, row_idx);
|
||||||
|
msg_select_cell(game, col_addr, row_addr);
|
||||||
|
if (msc_has_mine(cell)) {
|
||||||
|
printf("\nFound mine at %d/%d (%d/%c)", col_idx, row_idx, col_addr, row_addr);
|
||||||
|
msg_mark_selected_cell(game, MINE_DETECTED);
|
||||||
|
mine_cnt++;
|
||||||
|
} else {
|
||||||
|
msg_uncover_selected_cell(game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(mine_cnt == max, MSG("Expected %d mines but found %d", max, mine_cnt));
|
||||||
|
ASSERT_TRUE(msg_get_state(game) == SOLVED, MSG("Expected solved game"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_get_state__invalid_game) {
|
||||||
|
ASSERT_TRUE(msg_get_state(0) == INVALID, MSG("Expected invalid game"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_start_game__no_mine_distributed_before_the_first_cell_is_uncovered) {
|
||||||
|
Count max = 5;
|
||||||
|
MsGame game = msg_start_game(max, max, max);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
for (CellIdx row_idx = 0; row_idx < max; row_idx++) {
|
||||||
|
for (CellIdx col_idx = 0; col_idx < max; col_idx++) {
|
||||||
|
MsCell cell = msb_get_cell(msg_get_board(game), col_idx, row_idx);
|
||||||
|
ASSERT_FALSE(msc_has_mine(cell), MSG("Did not expect mine in cell %d / %d before first action", col_idx, row_idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ASSERT_FIRST_UNCOVERED_CELL_IS_EMPTY(size, col_idx, row_idx) _assert_first_cell_is_empty(tc, size, col_idx, row_idx)
|
||||||
|
void _assert_first_cell_is_empty(struct TestCase* tc, Count size, CellIdx col_idx, CellIdx row_idx) {
|
||||||
|
MsGame game = msg_start_game(size, size, size);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
RowAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
MsCell cell = msb_get_cell(msg_get_board(game), col_idx, row_idx);
|
||||||
|
ASSERT_TRUE(msg_select_cell(game, col_addr, row_addr), MSG("Expected that first cell can be selected"));
|
||||||
|
msg_uncover_selected_cell(game);
|
||||||
|
Count dnc = msc_get_dangerous_neighbor_count(cell);
|
||||||
|
ASSERT_TRUE(dnc == 0, MSG("Expected that first uncovered cell @ %d / %d is empty, but it pretends to have %d dangerous neighbors", col_idx, row_idx, dnc));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_start_game__first_uncovered_cell_is_empty) {
|
||||||
|
Count size = 5;
|
||||||
|
/* upper left corner */
|
||||||
|
ASSERT_FIRST_UNCOVERED_CELL_IS_EMPTY(size, 0, 0);
|
||||||
|
/* upper right corner */
|
||||||
|
ASSERT_FIRST_UNCOVERED_CELL_IS_EMPTY(size, size - 1, 0);
|
||||||
|
/* lower right corner */
|
||||||
|
ASSERT_FIRST_UNCOVERED_CELL_IS_EMPTY(size, size - 1, size - 1);
|
||||||
|
/* lower left corner */
|
||||||
|
ASSERT_FIRST_UNCOVERED_CELL_IS_EMPTY(size, 0, size - 1);
|
||||||
|
/* left border */
|
||||||
|
ASSERT_FIRST_UNCOVERED_CELL_IS_EMPTY(size, 0, size / 2);
|
||||||
|
/* top border */
|
||||||
|
ASSERT_FIRST_UNCOVERED_CELL_IS_EMPTY(size, size / 2, 0);
|
||||||
|
/* right border */
|
||||||
|
ASSERT_FIRST_UNCOVERED_CELL_IS_EMPTY(size, size - 1, size / 2);
|
||||||
|
/* bottom border */
|
||||||
|
ASSERT_FIRST_UNCOVERED_CELL_IS_EMPTY(size, size / 2, size - 1);
|
||||||
|
/* center */
|
||||||
|
ASSERT_FIRST_UNCOVERED_CELL_IS_EMPTY(size, size / 2, size / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_start_game__verify_dangerous_neighbor_counts) {
|
||||||
|
Count size = 6;
|
||||||
|
MsGame game = msg_start_game(size, size, size);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(1);
|
||||||
|
RowAddr row_addr = msu_idx_to_row_address(1);
|
||||||
|
MsBoard board = msg_get_board(game);
|
||||||
|
ASSERT_TRUE(msg_select_cell(game, col_addr, row_addr), MSG("Expected that first cell can be selected"));
|
||||||
|
msg_uncover_selected_cell(game); /* mines are distributed now */
|
||||||
|
bool checked_dnc = false;
|
||||||
|
for (int row_idx = 0; row_idx < size; row_idx++) {
|
||||||
|
for (int col_idx = 0; col_idx < size; col_idx++) {
|
||||||
|
MsCell cell = msb_get_cell(board, col_idx, row_idx);
|
||||||
|
Count dnc = msc_get_dangerous_neighbor_count(cell);
|
||||||
|
if (!msc_has_mine(cell) && dnc != 0) {
|
||||||
|
Count act_mine_cnt = 0;
|
||||||
|
for (CellIdx nr = (row_idx == 0 ? 0 : row_idx - 1); nr <= row_idx + 1; nr ++) {
|
||||||
|
for (CellIdx nc = (col_idx == 0 ? 0 : col_idx - 1); nc <= col_idx + 1; nc ++) {
|
||||||
|
MsCell neighbor = msb_get_cell(board, nc, nr);
|
||||||
|
if (!(nc == col_idx && nr == row_idx) && msc_is_valid(cell)) {
|
||||||
|
act_mine_cnt += (msc_has_mine(neighbor) ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checked_dnc = true;
|
||||||
|
ASSERT_TRUE(dnc == act_mine_cnt, MSG("Expected that cell @ %d / %d has %d dangerous neighbors but found %d mines around", col_idx, row_idx, dnc, act_mine_cnt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(checked_dnc, MSG("Expected that at least one dangerous neighbor count was evaluated"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_get_mines_left_count) {
|
||||||
|
Count max = 5;
|
||||||
|
Count detection_cnt = 0;
|
||||||
|
MsGame game = msg_start_game(max, max, max);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
Count mines_left = msg_get_mines_left_count(game);
|
||||||
|
ASSERT_TRUE(mines_left == max, MSG("Expected initially %d mines left but found %d", max, mines_left));
|
||||||
|
for (CellIdx row_idx = 0; row_idx < max; row_idx++) {
|
||||||
|
for (CellIdx col_idx = 0; col_idx < max; col_idx++) {
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
RowAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
MsCell cell = msb_get_cell(msg_get_board(game), col_idx, row_idx);
|
||||||
|
if (msc_has_mine(cell)) {
|
||||||
|
msg_select_cell(game, col_addr, row_addr);
|
||||||
|
msg_mark_selected_cell(game, MINE_DETECTED);
|
||||||
|
detection_cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mines_left = msg_get_mines_left_count(game);
|
||||||
|
ASSERT_TRUE(mines_left == max - detection_cnt, MSG("Found a mine: Expected %d mines left but found %d", max - detection_cnt, mines_left));
|
||||||
|
msg_mark_selected_cell(game, MINE_DETECTED);
|
||||||
|
mines_left = msg_get_mines_left_count(game);
|
||||||
|
ASSERT_TRUE(mines_left == max - detection_cnt, MSG("Found a mine: Expected %d mines left after marking invalid cell but found %d", max - detection_cnt, mines_left));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_get_mines_suspected_count) {
|
||||||
|
Count max = 4;
|
||||||
|
Count exp_suspected = 0;
|
||||||
|
MsGame game = msg_start_game(max, max, max);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
Count act_suspected = msg_get_mines_suspected_count(game);
|
||||||
|
ASSERT_TRUE(act_suspected == 0, MSG("Expected initially %d suspicons but found %d", 0, act_suspected));
|
||||||
|
for (CellIdx row_idx = 1; row_idx < max; row_idx += 2) {
|
||||||
|
for (CellIdx col_idx = 0; col_idx < max; col_idx += 2) {
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
RowAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
msg_select_cell(game, col_addr, row_addr);
|
||||||
|
msg_mark_selected_cell(game, MINE_SUSPECTED);
|
||||||
|
exp_suspected++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
act_suspected = msg_get_mines_suspected_count(game);
|
||||||
|
ASSERT_TRUE(exp_suspected == act_suspected, MSG("Expected %d suspicons but found %d", exp_suspected, act_suspected));
|
||||||
|
msg_mark_selected_cell(game, MINE_SUSPECTED);
|
||||||
|
act_suspected = msg_get_mines_suspected_count(game);
|
||||||
|
ASSERT_TRUE(exp_suspected == act_suspected, MSG("Expected %d suspicons after marking invalid cell but found %d", exp_suspected, act_suspected));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_get_uncover_action_count) {
|
||||||
|
Count max = 4;
|
||||||
|
Count exp_action_cnt = 0;
|
||||||
|
MsGame game = msg_start_game(max, max, max);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
Count act_action_cnt = msg_get_uncover_action_count(game);
|
||||||
|
ASSERT_TRUE(exp_action_cnt == act_action_cnt, MSG("Expected %d uncover actions but found %d", exp_action_cnt, act_action_cnt));
|
||||||
|
for (CellIdx row_idx = 0; row_idx < max; row_idx += 2) {
|
||||||
|
for (CellIdx col_idx = 1; col_idx < max; col_idx += 2) {
|
||||||
|
ColAddr col_addr = msu_idx_to_col_address(col_idx);
|
||||||
|
RowAddr row_addr = msu_idx_to_row_address(row_idx);
|
||||||
|
MsCell cell = msb_get_cell(msg_get_board(game), col_idx, row_idx);
|
||||||
|
if (msc_is_covered(cell) && !msc_has_mine(cell)) {
|
||||||
|
printf("\nUncovering %d. cell @ %d / %d", exp_action_cnt + 1, col_idx, row_idx);
|
||||||
|
msg_select_cell(game, col_addr, row_addr);
|
||||||
|
msg_uncover_selected_cell(game);
|
||||||
|
exp_action_cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
act_action_cnt = msg_get_uncover_action_count(game);
|
||||||
|
ASSERT_TRUE(exp_action_cnt == act_action_cnt, MSG("Expected %d uncover actions but got %d", exp_action_cnt, act_action_cnt));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(test_msg_get_board__provides_valid_board__for_valid_game) {
|
||||||
|
MsGame game = msg_start_configured_game(BEGINNER);
|
||||||
|
ASSERT_TRUE(msg_is_valid(game), MSG("Expected valid game"));
|
||||||
|
ASSERT_TRUE(msb_is_valid(msg_get_board(game)), MSG("Expected valid board"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_msg_get_board__provides_invalid_board__for_invalid_game) {
|
||||||
|
ASSERT_FALSE(msb_is_valid(msg_get_board(0)), MSG("Expected invalid board"));
|
||||||
|
}
|
||||||
57
test_ms_game.h
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Klasse: n/a
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Unit Tests for Mine Sweeper Game
|
||||||
|
* Author: S. Schraml
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Test functions for ADT MsGame.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
#ifndef ___TEST_MS_GAME_H
|
||||||
|
#define ___TEST_MS_GAME_H
|
||||||
|
|
||||||
|
#include "shortcut.h"
|
||||||
|
|
||||||
|
TEST(test_msg_start_configured_game__shall_provide_valid_game);
|
||||||
|
TEST(test_msg_start_game__shall_provide_valid_game__for_valid_arguments);
|
||||||
|
|
||||||
|
TEST(test_msg_is_valid__shall_be_true__for_valid_game);
|
||||||
|
TEST(test_msg_is_valid__shall_be_false__for_0_game);
|
||||||
|
TEST(test_msg_is_valid__shall_be_false__for_game_with_zero_sized_board);
|
||||||
|
|
||||||
|
TEST(test_msg_select_cell__shall_be_true__for_covered_cell_in_range);
|
||||||
|
TEST(test_msg_select_cell__shall_be_false__for_invalid_game);
|
||||||
|
TEST(test_msg_select_cell__shall_be_false__for_uncovered_cell_in_range);
|
||||||
|
|
||||||
|
TEST(test_msg_get_select_cell__shall_provide_cell__for_valid_selection);
|
||||||
|
TEST(test_msg_get_select_cell__shall_provide_0__for_invalid_selection);
|
||||||
|
|
||||||
|
TEST(test_msg_uncover_selected_cell__shall_uncover_selected_cell);
|
||||||
|
TEST(test_msg_uncover_selected_cell__shall_ignore_covered_cell);
|
||||||
|
TEST(test_msg_uncover_selected_cell__shall_not_crash_on_invalid_game);
|
||||||
|
|
||||||
|
TEST(test_msg_mark_selected_cell__shall_mark_selected_cell_and_ignore_subsequent_calls);
|
||||||
|
TEST(test_msg_mark_selected_cell__shall_ignore_mine_detection_if_number_of_mines_is_reached);
|
||||||
|
TEST(test_msg_mark_selected_cell__shall_clear_detection_marker);
|
||||||
|
TEST(test_msg_mark_selected_cell__shall_clear_suspicion_marker);
|
||||||
|
|
||||||
|
TEST(test_msg_get_state__in_progress);
|
||||||
|
TEST(test_msg_get_state__failed);
|
||||||
|
TEST(test_msg_get_state__success);
|
||||||
|
TEST(test_msg_get_state__invalid_game);
|
||||||
|
|
||||||
|
TEST(test_msg_start_game__is_initialization_valid);
|
||||||
|
TEST(test_msg_start_game__no_mine_distributed_before_the_first_cell_is_uncovered);
|
||||||
|
TEST(test_msg_start_game__first_uncovered_cell_is_empty);
|
||||||
|
TEST(test_msg_start_game__verify_dangerous_neighbor_counts);
|
||||||
|
|
||||||
|
TEST(test_msg_get_mines_left_count);
|
||||||
|
TEST(test_msg_get_mines_suspected_count);
|
||||||
|
TEST(test_msg_get_uncover_action_count);
|
||||||
|
|
||||||
|
TEST(test_msg_get_board__provides_valid_board__for_valid_game);
|
||||||
|
TEST(test_msg_get_board__provides_invalid_board__for_invalid_game);
|
||||||
|
|
||||||
|
#endif
|
||||||
95
test_ms_ui_utils.c
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Klasse: n/a
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Implementation of UTs for Mine Sweeper UI utilities
|
||||||
|
* Author: S. Schraml
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Test functions for Mine Sweeper UI utilities.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "test_ms_ui_utils.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "shortcut.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "ms_ui_utils.h"
|
||||||
|
|
||||||
|
TEST(test_random) {
|
||||||
|
Count cnt = 100;
|
||||||
|
Count limit = 10;
|
||||||
|
CellIdx indexes[10] = {0};
|
||||||
|
|
||||||
|
msu_init_rand();
|
||||||
|
ASSERT_TRUE(msu_get_random_index(0) == 0, MSG("Expected 0 index for 0 upper limit"));
|
||||||
|
for (Count i = 0; i < cnt; i++) {
|
||||||
|
CellIdx idx = msu_get_random_index(limit);
|
||||||
|
if (idx >= 0 && idx < limit) {
|
||||||
|
indexes[idx]++;
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(idx >= 0 && idx < limit, MSG("Expected random index in range from 0 to %d but was %d", limit - 1, idx));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < sizeof(indexes)/sizeof(indexes[0]); i++) {
|
||||||
|
printf("\nRandom number up to %d - %d: %2d of %d", limit -1, i, indexes[i], cnt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_convert_column_addr) {
|
||||||
|
CellIdx exp_indexes[] = {0, 2, MAX_BOARD_SIZE - 1};
|
||||||
|
for (int i = 0; i < sizeof(exp_indexes) / sizeof(exp_indexes[0]); i++) {
|
||||||
|
ColAddr addr = msu_idx_to_col_address(exp_indexes[i]);
|
||||||
|
CellIdx act_idx = msu_col_address_to_index(addr);
|
||||||
|
ASSERT_TRUE(exp_indexes[i] == act_idx, MSG("Expected idx %d but was %d", exp_indexes[i], act_idx));
|
||||||
|
}
|
||||||
|
ColAddr addr = msu_idx_to_col_address(MAX_BOARD_SIZE);
|
||||||
|
CellIdx act_idx = msu_col_address_to_index(addr);
|
||||||
|
ASSERT_TRUE(0 == act_idx, MSG("Expected idx 0 for invalid address but was %d", act_idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_convert_actions) {
|
||||||
|
Action actions[] = { MARK_MINE, MARK_SUSPECT, CLEAR_MARKER, UNCOVER, QUIT_GAME };
|
||||||
|
for (int i = 0; i < sizeof(actions)/sizeof(actions[0]); i++) {
|
||||||
|
char key = msu_get_action_char(actions[i]);
|
||||||
|
Action act = msu_get_action(key);
|
||||||
|
ASSERT_TRUE(act == actions[i], MSG("Expected action %d is converted successfully", actions[i]));
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(msu_get_action_char(UNKNOWN) == '\0', MSG("Expected '\0' key for action UNKNOWN"));
|
||||||
|
ASSERT_TRUE(msu_get_action('\0') == UNKNOWN, MSG("Expected action UNKNOWN for \0 key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_convert_row_addr) {
|
||||||
|
CellIdx exp_indexes[] = {0, 2, MAX_BOARD_SIZE - 1};
|
||||||
|
for (int i = 0; i < sizeof(exp_indexes) / sizeof(exp_indexes[0]); i++) {
|
||||||
|
RowAddr addr = msu_idx_to_row_address(exp_indexes[i]);
|
||||||
|
CellIdx act_idx = msu_row_address_to_index(addr);
|
||||||
|
ASSERT_TRUE(exp_indexes[i] == act_idx, MSG("Expected idx %d but was %d", exp_indexes[i], act_idx));
|
||||||
|
}
|
||||||
|
RowAddr addr = msu_idx_to_row_address(MAX_BOARD_SIZE);
|
||||||
|
CellIdx act_idx = msu_row_address_to_index(addr);
|
||||||
|
ASSERT_TRUE(0 == act_idx, MSG("Expected idx 0 for invalid address but was %d", act_idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_get_marker_symbol) {
|
||||||
|
CellMarker exp_markers[] = {NONE, MINE_DETECTED, MINE_SUSPECTED};
|
||||||
|
for (int i = 0; i < sizeof(exp_markers) / sizeof(exp_markers[0]); i++) {
|
||||||
|
ASSERT_FALSE(msu_get_marker_symbol(exp_markers[i]) == '\0', MSG("Any marker character expected for marker %d", exp_markers[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_get_mine_symbol) {
|
||||||
|
ASSERT_TRUE(msu_get_mine_symbol != '\0', MSG("Expected symbol for a mine"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(test_get_status_label) {
|
||||||
|
GameState states[] = { INVALID, IN_PROGRESS, SOLVED, FAILED };
|
||||||
|
for (int i = 0; i < sizeof(states)/sizeof(states[0]); i++) {
|
||||||
|
char* label = msu_get_status_label(states[i]);
|
||||||
|
ASSERT_TRUE(label != 0, MSG("Expected a non-0 label for status %d", states[i]));
|
||||||
|
ASSERT_TRUE(strnlen(label, 128) < 127, MSG("Expected a valid label for status %d", states[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
26
test_ms_ui_utils.h
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*----------------------------------------------------------
|
||||||
|
* HTBLA-Leonding / Klasse: n/a
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* Exercise Number: B1
|
||||||
|
* Title: Unit Tests for Mine Sweeper UI utilities
|
||||||
|
* Author: S. Schraml
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
* Description:
|
||||||
|
* Test functions for Mine Sweeper UI Utilities.
|
||||||
|
* ----------------------------------------------------------
|
||||||
|
*/
|
||||||
|
#ifndef ___TEST_MS_UI_UTILS_H
|
||||||
|
#define ___TEST_MS_UI_UTILS_H
|
||||||
|
|
||||||
|
#include "shortcut.h"
|
||||||
|
|
||||||
|
TEST(test_random);
|
||||||
|
TEST(test_convert_column_addr);
|
||||||
|
TEST(test_convert_row_addr);
|
||||||
|
TEST(test_convert_actions);
|
||||||
|
TEST(test_get_marker_symbol);
|
||||||
|
TEST(test_get_mine_symbol);
|
||||||
|
TEST(test_get_status_label);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||